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

igor-krechetov / hsmcpp / 4815527644

pending completion
4815527644

push

github

igor-krechetov
[0.35.0][r] Variant refactoring and custom types support (BREAKS INTERFACE)

329 of 329 new or added lines in 2 files covered. (100.0%)

3586 of 7885 relevant lines covered (45.48%)

49379.18 hits per line

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

89.96
/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/os/os.hpp"
9
#include "hsmcpp/IHsmEventDispatcher.hpp"
10
#include "hsmcpp/logging.hpp"
11

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

16
#ifdef HSMBUILD_DEBUGGING
17
  #include <chrono>
18
  #include <cstdlib>
19
  #include <cstring>
20

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

32
#ifndef HSM_DISABLE_DEBUG_TRACES
33
  #define DEBUG_DUMP_ACTIVE_STATES() dumpActiveStates()
34
#else
35
  #define DEBUG_DUMP_ACTIVE_STATES()
36
#endif
37

38
// NOTE: used only for logging during development. in release mode macros is empty
39
// cppcheck-suppress misra-c2012-8.4
40
HSM_TRACE_PREINIT()
41

42
#undef HSM_TRACE_CLASS
43
#define HSM_TRACE_CLASS "HierarchicalStateMachine"
44

45
// If defined, HSM will performe safety checks during states and substates registration.
46
// Normally HSM structure should be static, so this feature is usefull only
47
// during development since it reduces performance a bit
48
// #define HSM_ENABLE_SAFE_STRUCTURE                    1
49

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

62
#define ENV_DUMPPATH "HSMCPP_DUMP_PATH"
63
#define DEFAULT_DUMP_PATH "./dump.hsmlog"
64

65
#define HsmEventStatus_t HierarchicalStateMachine::Impl::HsmEventStatus
66

67
namespace hsmcpp {
68

69
// ============================================================================
70
// PUBLIC
71
// ============================================================================
72
HierarchicalStateMachine::Impl::Impl(HierarchicalStateMachine* parent, const StateID_t initialState)
2,060✔
73
    // cppcheck-suppress misra-c2012-10.4 ; false-positive. thinks that ':' is arithmetic operation
74
    : mParent(parent)
2,060✔
75
    , mInitialState(initialState) {
2,060✔
76
    HSM_TRACE_INIT();
2,060✔
77
}
2,060✔
78

79
HierarchicalStateMachine::Impl::~Impl() {
4,100✔
80
    release();
2,050✔
81
}
8,410✔
82

83
void HierarchicalStateMachine::Impl::resetParent() {
2,050✔
84
#ifndef HSM_DISABLE_THREADSAFETY
85
    LockGuard lk(mParentSync);
2,050✔
86
#endif
87

88
    mParent = nullptr;
2,050✔
89
}
2,050✔
90

91
void HierarchicalStateMachine::Impl::setInitialState(const StateID_t initialState) {
410✔
92
    if (true == mDispatcher.expired()) {
410✔
93
        mInitialState = initialState;
350✔
94
    }
95
}
410✔
96

97
bool HierarchicalStateMachine::Impl::initialize(const std::weak_ptr<IHsmEventDispatcher>& dispatcher) {
2,060✔
98
    HSM_TRACE_CALL_DEBUG();
2,060✔
99
    bool result = false;
2,060✔
100

101
    if (true == mDispatcher.expired()) {
2,060✔
102
        auto dispatcherPtr = dispatcher.lock();
2,060✔
103

104
        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
105
        if (dispatcherPtr) {
2,060✔
106
            if (true == dispatcherPtr->start()) {
2,060✔
107
                std::weak_ptr<Impl> ptrInstance;
2,060✔
108

109
                HSM_TRY {
2,060✔
110
                    ptrInstance = shared_from_this();
4,120✔
111
                } HSM_CATCH (const std::bad_weak_ptr& e) {
×
112
                    HSM_TRACE_ERROR("Impl instance must be created as shared_ptr");
×
113
                }
×
114

115
                if (false == ptrInstance.expired()) {
4,120✔
116
                    mDispatcher = dispatcher;
2,060✔
117

118
                    // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
119
                    mEventsHandlerId = dispatcherPtr->registerEventHandler([ptrInstance]() {
49,514✔
120
                        bool handlerIsValid = false;
1,290,075✔
121
                        auto pThis = ptrInstance.lock();
1,290,075✔
122

123
                        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
124
                        if (pThis && (false == pThis->mStopDispatching)) {
1,290,075✔
125
                            pThis->dispatchEvents();
1,289,551✔
126
                            handlerIsValid = true;
127
                        }
128

129
                        // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
130
                        // cppcheck-suppress misra-c2012-15.5
131
                        return handlerIsValid;
1,290,075✔
132
                    });
1,290,075✔
133

134
                    // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
135
                    mTimerHandlerId = dispatcherPtr->registerTimerHandler([ptrInstance](const TimerID_t timerId) {
16,870✔
136
                        bool handlerIsValid = false;
130✔
137
                        auto pThis = ptrInstance.lock();
130✔
138

139
                        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
140
                        if (pThis && (false == pThis->mStopDispatching)) {
130✔
141
                            pThis->dispatchTimerEvent(timerId);
130✔
142
                            handlerIsValid = true;
143
                        }
144

145
                        // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
146
                        // cppcheck-suppress misra-c2012-15.5
147
                        return handlerIsValid;
130✔
148
                    });
130✔
149

150
                    // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
151
                    mEnqueuedEventsHandlerId = dispatcherPtr->registerEnqueuedEventHandler([ptrInstance](const EventID_t event) {
16,510✔
152
                        bool handlerIsValid = false;
10✔
153
                        auto pThis = ptrInstance.lock();
10✔
154

155
                        // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
156
                        if (pThis && (false == pThis->mStopDispatching)) {
10✔
157
                            pThis->transitionSimple(event);
10✔
158
                            handlerIsValid = true;
159
                        }
160

161
                        // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
162
                        // cppcheck-suppress misra-c2012-15.5
163
                        return handlerIsValid;
10✔
164
                    });
10✔
165

166
                    if ((INVALID_HSM_DISPATCHER_HANDLER_ID != mEventsHandlerId) &&
2,060✔
167
                        (INVALID_HSM_DISPATCHER_HANDLER_ID != mTimerHandlerId)) {
2,060✔
168
                        logHsmAction(HsmLogAction::IDLE,
2,060✔
169
                                    INVALID_HSM_STATE_ID,
170
                                    INVALID_HSM_STATE_ID,
171
                                    INVALID_HSM_EVENT_ID,
172
                                    false,
173
                                    VariantVector_t());
2,060✔
174
                        handleStartup();
2,060✔
175
                        result = true;
176
                    } else {
177
                        HSM_TRACE_ERROR("failed to register event handlers");
×
178
                        dispatcherPtr->unregisterEventHandler(mEventsHandlerId);
×
179
                        dispatcherPtr->unregisterEnqueuedEventHandler(mEnqueuedEventsHandlerId);
×
180
                        dispatcherPtr->unregisterTimerHandler(mTimerHandlerId);
×
181
                    }
182
                }
183
            } else {
2,060✔
184
                HSM_TRACE_ERROR("failed to start dispatcher");
185
            }
186
        } else {
187
            HSM_TRACE_ERROR("dispatcher is NULL");
2,060✔
188
        }
189
    } else {
2,060✔
190
        HSM_TRACE_ERROR("already initialized");
2,060✔
191
    }
192

193
    return result;
2,060✔
194
}
195

196
bool HierarchicalStateMachine::Impl::isInitialized() const {
×
197
    return (false == mDispatcher.expired());
×
198
}
199

200
void HierarchicalStateMachine::Impl::release() {
4,100✔
201
    mStopDispatching = true;
4,100✔
202
    HSM_TRACE_CALL_DEBUG();
4,100✔
203

204
    disableHsmDebugging();
4,100✔
205

206
    auto dispatcherPtr = mDispatcher.lock();
4,100✔
207

208
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
209
    if (dispatcherPtr) {
4,100✔
210
        dispatcherPtr->unregisterEventHandler(mEventsHandlerId);
2,050✔
211
        dispatcherPtr->unregisterEnqueuedEventHandler(mEnqueuedEventsHandlerId);
2,050✔
212
        dispatcherPtr->unregisterTimerHandler(mTimerHandlerId);
2,050✔
213
        mDispatcher.reset();
2,050✔
214
        mEventsHandlerId = INVALID_HSM_DISPATCHER_HANDLER_ID;
2,050✔
215
    }
216
}
4,100✔
217

218
void HierarchicalStateMachine::Impl::registerFailedTransitionCallback(const HsmTransitionFailedCallback_t& onFailedTransition) {
210✔
219
    mFailedTransitionCallback = onFailedTransition;
210✔
220
}
210✔
221

222
void HierarchicalStateMachine::Impl::registerState(const StateID_t state,
5,730✔
223
                                                   HsmStateChangedCallback_t onStateChanged,
224
                                                   HsmStateEnterCallback_t onEntering,
225
                                                   HsmStateExitCallback_t onExiting) {
226
#ifdef HSM_ENABLE_SAFE_STRUCTURE
227
    if ((false == isSubstate(state)) && (false == isTopState(state))) {
5,730✔
228
        mTopLevelStates.push_back(state);
×
229
    }
230
#endif  // HSM_ENABLE_SAFE_STRUCTURE
231

232
    if (onStateChanged || onEntering || onExiting) {
5,730✔
233
        StateCallbacks cb;
4,900✔
234

235
        cb.onStateChanged = onStateChanged;
4,900✔
236
        cb.onEntering = onEntering;
4,900✔
237
        cb.onExiting = onExiting;
4,900✔
238
        mRegisteredStates[state] = cb;
4,900✔
239

240
        HSM_TRACE_CALL_DEBUG_ARGS("mRegisteredStates.size=%ld", mRegisteredStates.size());
4,900✔
241
    }
4,900✔
242
}
5,730✔
243

244
void HierarchicalStateMachine::Impl::registerFinalState(const StateID_t state,
270✔
245
                                                        const EventID_t event,
246
                                                        HsmStateChangedCallback_t onStateChanged,
247
                                                        HsmStateEnterCallback_t onEntering,
248
                                                        HsmStateExitCallback_t onExiting) {
249
    mFinalStates.emplace(state, event);
270✔
250
    registerState(state, onStateChanged, onEntering, onExiting);
540✔
251
}
270✔
252

253
void HierarchicalStateMachine::Impl::registerHistory(const StateID_t parent,
100✔
254
                                                     const StateID_t historyState,
255
                                                     const HistoryType type,
256
                                                     const StateID_t defaultTarget,
257
                                                     HsmTransitionCallback_t transitionCallback) {
258
    (void)mHistoryStates.emplace(parent, historyState);
100✔
259
    mHistoryData.emplace(historyState, HistoryInfo(type, defaultTarget, transitionCallback));
100✔
260
}
100✔
261

262
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent, const StateID_t substate) {
740✔
263
    return registerSubstate(parent, substate, false);
740✔
264
}
265

266
bool HierarchicalStateMachine::Impl::registerSubstateEntryPoint(const StateID_t parent,
970✔
267
                                                                const StateID_t substate,
268
                                                                const EventID_t onEvent,
269
                                                                const HsmTransitionConditionCallback_t& conditionCallback,
270
                                                                const bool expectedConditionValue) {
271
    return registerSubstate(parent, substate, true, onEvent, conditionCallback, expectedConditionValue);
970✔
272
}
273

274
void HierarchicalStateMachine::Impl::registerTimer(const TimerID_t timerID, const EventID_t event) {
140✔
275
    mTimers.emplace(timerID, event);
140✔
276
}
140✔
277

278
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent,
1,710✔
279
                                                      const StateID_t substate,
280
                                                      const bool isEntryPoint,
281
                                                      const EventID_t onEvent,
282
                                                      const HsmTransitionConditionCallback_t& conditionCallback,
283
                                                      const bool expectedConditionValue) {
284
    bool registrationAllowed = false;
1,710✔
285

286
#ifdef HSM_ENABLE_SAFE_STRUCTURE
287
    // do a simple sanity check
288
    if (parent != substate) {
1,710✔
289
        StateID_t curState = parent;
1,710✔
290
        StateID_t prevState;
1,710✔
291

292
        if (false == hasParentState(substate, prevState)) {
1,710✔
293
            registrationAllowed = true;
2,140✔
294

295
            while (true == hasParentState(curState, prevState)) {
2,140✔
296
                if (substate == prevState) {
470✔
297
                    HSM_TRACE_CALL_DEBUG_ARGS(
298
                        "requested operation will result in substates recursion (parent=<%s>, substate=<%s>)",
299
                        getStateName(parent).c_str(),
300
                        getStateName(substate).c_str());
301
                    registrationAllowed = false;
302
                    break;
303
                }
304

305
                curState = prevState;
306
            }
307
        } else {
308
            HSM_TRACE_CALL_DEBUG_ARGS("substate <%s> already has a parent <%s>",
309
                                      getStateName(substate).c_str(),
310
                                      getStateName(prevState).c_str());
1,710✔
311
        }
312
    }
313
#else
314
    registrationAllowed = (parent != substate);
315
#endif  // HSM_ENABLE_SAFE_STRUCTURE
316

317
    if (registrationAllowed) {
1,710✔
318
        // NOTE: false-positive. isEntryPoint is of type bool
319
        // cppcheck-suppress misra-c2012-14.4
320
        if (isEntryPoint) {
1,670✔
321
            StateEntryPoint entryInfo;
930✔
322

323
            entryInfo.state = substate;
930✔
324
            entryInfo.onEvent = onEvent;
930✔
325
            entryInfo.checkCondition = conditionCallback;
930✔
326
            entryInfo.expectedConditionValue = expectedConditionValue;
930✔
327

328
            (void)mSubstateEntryPoints.emplace(parent, entryInfo);
930✔
329
        }
930✔
330

331
        (void)mSubstates.emplace(parent, substate);
1,670✔
332

333
#ifdef HSM_ENABLE_SAFE_STRUCTURE
334
        if (true == isTopState(substate)) {
1,670✔
335
            mTopLevelStates.remove(substate);
1,670✔
336
        }
337
#endif  // HSM_ENABLE_SAFE_STRUCTURE
338
    }
339

340
    return registrationAllowed;
1,710✔
341
}
342

343
bool HierarchicalStateMachine::Impl::registerStateAction(const StateID_t state,
220✔
344
                                                         const StateActionTrigger actionTrigger,
345
                                                         const StateAction action,
346
                                                         const VariantVector_t& args) {
347
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>, actionTrigger=%d, action=%d",
348
                              getStateName(state).c_str(),
349
                              SC2INT(actionTrigger),
350
                              SC2INT(action));
220✔
351
    bool result = false;
220✔
352
    bool argsValid = false;
220✔
353
    StateActionInfo newAction;
220✔
354

355
    newAction.actionArgs = args;
220✔
356

357
    // validate arguments
358
    switch (action) {
220✔
359
        case StateAction::START_TIMER:
100✔
360
            argsValid = (newAction.actionArgs.size() == 3u) && newAction.actionArgs[0].isNumeric() &&
200✔
361
                        newAction.actionArgs[1].isNumeric() && newAction.actionArgs[2].isBool();
300✔
362
            break;
363
        case StateAction::RESTART_TIMER:
20✔
364
        case StateAction::STOP_TIMER:
20✔
365
            argsValid = (newAction.actionArgs.size() == 1u) && newAction.actionArgs[0].isNumeric();
20✔
366
            break;
367
        case StateAction::TRANSITION:
100✔
368
            argsValid = (newAction.actionArgs.size() >= 1u) && newAction.actionArgs[0].isNumeric();
100✔
369
            break;
370
        default:
371
            // do nothing
372
            break;
373
    }
374

375
    if (true == argsValid) {
376
        newAction.action = action;
220✔
377
        (void)mRegisteredActions.emplace(std::make_pair(state, actionTrigger), newAction);
220✔
378
        result = true;
220✔
379
    } else {
380
        HSM_TRACE_ERROR("invalid arguments");
220✔
381
    }
382

383
    return result;
220✔
384
}
220✔
385

386
void HierarchicalStateMachine::Impl::registerTransition(const StateID_t from,
4,730✔
387
                                                        const StateID_t to,
388
                                                        const EventID_t onEvent,
389
                                                        HsmTransitionCallback_t transitionCallback,
390
                                                        HsmTransitionConditionCallback_t conditionCallback,
391
                                                        const bool expectedConditionValue) {
392
    (void)mTransitionsByEvent.emplace(std::make_pair(from, onEvent),
4,730✔
393
                                      TransitionInfo(from,
4,730✔
394
                                                     to,
395
                                                     TransitionType::EXTERNAL_TRANSITION,
396
                                                     transitionCallback,
397
                                                     conditionCallback,
398
                                                     expectedConditionValue));
399
}
4,730✔
400

401
void HierarchicalStateMachine::Impl::registerSelfTransition(const StateID_t state,
90✔
402
                                                            const EventID_t onEvent,
403
                                                            const TransitionType type,
404
                                                            HsmTransitionCallback_t transitionCallback,
405
                                                            HsmTransitionConditionCallback_t conditionCallback,
406
                                                            const bool expectedConditionValue) {
407
    (void)mTransitionsByEvent.emplace(
180✔
408
        std::make_pair(state, onEvent),
90✔
409
        TransitionInfo(state, state, type, transitionCallback, conditionCallback, expectedConditionValue));
90✔
410
}
90✔
411

412
StateID_t HierarchicalStateMachine::Impl::getLastActiveState() const {
40✔
413
    StateID_t currentState = INVALID_HSM_STATE_ID;
40✔
414

415
    if (false == mActiveStates.empty()) {
40✔
416
        currentState = mActiveStates.back();
40✔
417
    }
418

419
    return currentState;
40✔
420
}
421

422
const std::list<StateID_t>& HierarchicalStateMachine::Impl::getActiveStates() const {
2,270✔
423
    return mActiveStates;
2,270✔
424
}
425

426
bool HierarchicalStateMachine::Impl::isStateActive(const StateID_t state) const {
2,090,278✔
427
    return (std::find(mActiveStates.begin(), mActiveStates.end(), state) != mActiveStates.end());
2,090,278✔
428
}
429

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

433
    (void)transitionExWithArgsArray(event, false, false, 0, args);
100✔
434
}
100✔
435

436
bool HierarchicalStateMachine::Impl::transitionExWithArgsArray(const EventID_t event,
1,001,750✔
437
                                                               const bool clearQueue,
438
                                                               const bool sync,
439
                                                               const int timeoutMs,
440
                                                               const VariantVector_t& args) {
441
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, clearQueue=%s, sync=%s, args.size=%lu",
442
                              getEventName(event).c_str(),
443
                              BOOL2STR(clearQueue),
444
                              BOOL2STR(sync),
445
                              args.size());
1,001,750✔
446

447
    bool status = false;
1,001,750✔
448
    auto dispatcherPtr = mDispatcher.lock();
1,001,750✔
449

450
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
451
    if (dispatcherPtr) {
1,001,750✔
452
        PendingEventInfo eventInfo;
1,001,740✔
453

454
        eventInfo.type = event;
1,001,740✔
455
        eventInfo.args = args;
1,001,740✔
456

457
        if (true == sync) {
1,001,740✔
458
            eventInfo.initLock();
1,150✔
459
        }
460

461
        {
1,001,740✔
462
            HSM_SYNC_EVENTS_QUEUE();
1,001,740✔
463

464
            if (true == clearQueue) {
1,001,740✔
465
                clearPendingEvents();
10✔
466
            }
467

468
            mPendingEvents.push_back(eventInfo);
1,001,740✔
469
        }
1,001,740✔
470

471
        HSM_TRACE_DEBUG("transitionEx: emit");
1,001,740✔
472
        dispatcherPtr->emitEvent(mEventsHandlerId);
1,001,740✔
473

474
        if (true == sync) {
1,001,740✔
475
            HSM_TRACE_DEBUG("transitionEx: wait...");
1,150✔
476
            eventInfo.wait(timeoutMs);
1,150✔
477
            status = (HsmEventStatus_t::DONE_OK == *eventInfo.transitionStatus);
1,150✔
478
        } else {
479
            // always return true for async transitions
480
            status = true;
481
        }
482
    } else {
1,001,740✔
483
        HSM_TRACE_ERROR("HSM is not initialized");
1,001,750✔
484
    }
485

486
    return status;
1,001,750✔
487
}
1,001,750✔
488

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

494
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
495
    if (dispatcherPtr) {
10✔
496
        res = dispatcherPtr->enqueueEvent(mEnqueuedEventsHandlerId, event);
10✔
497
    }
498

499
    return res;
10✔
500
}
10✔
501

502
bool HierarchicalStateMachine::Impl::isTransitionPossible(const EventID_t event, const VariantVector_t& args) {
60✔
503
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
504
    bool possible = false;
60✔
505

506
    for (auto it = mActiveStates.begin(); it != mActiveStates.end(); ++it) {
100✔
507
        possible = checkTransitionPossibility(*it, event, args);
60✔
508

509
        if (true == possible) {
60✔
510
            break;
511
        }
512
    }
513

514
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
515
    return possible;
60✔
516
}
517

518
void HierarchicalStateMachine::Impl::startTimer(const TimerID_t timerID,
40✔
519
                                                const unsigned int intervalMs,
520
                                                const bool isSingleShot) {
521
    auto dispatcherPtr = mDispatcher.lock();
40✔
522

523
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
524
    if (dispatcherPtr) {
40✔
525
        dispatcherPtr->startTimer(mTimerHandlerId, timerID, intervalMs, isSingleShot);
40✔
526
    }
527
}
40✔
528

529
void HierarchicalStateMachine::Impl::restartTimer(const TimerID_t timerID) {
10✔
530
    auto dispatcherPtr = mDispatcher.lock();
10✔
531

532
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
533
    if (dispatcherPtr) {
10✔
534
        dispatcherPtr->restartTimer(timerID);
10✔
535
    }
536
}
10✔
537

538
void HierarchicalStateMachine::Impl::stopTimer(const TimerID_t timerID) {
20✔
539
    auto dispatcherPtr = mDispatcher.lock();
20✔
540

541
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
542
    if (dispatcherPtr) {
20✔
543
        dispatcherPtr->stopTimer(timerID);
20✔
544
    }
545
}
20✔
546

547
bool HierarchicalStateMachine::Impl::isTimerRunning(const TimerID_t timerID) {
30✔
548
    bool running = false;
30✔
549
    auto dispatcherPtr = mDispatcher.lock();
30✔
550

551
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
552
    if (dispatcherPtr) {
30✔
553
        running = dispatcherPtr->isTimerRunning(timerID);
30✔
554
    }
555

556
    return running;
30✔
557
}
30✔
558

559
// ============================================================================
560
// PRIVATE
561
// ============================================================================
562
void HierarchicalStateMachine::Impl::handleStartup() {
2,060✔
563
    HSM_TRACE_CALL_DEBUG_ARGS("mActiveStates.size=%ld", mActiveStates.size());
2,060✔
564
    auto dispatcherPtr = mDispatcher.lock();
2,060✔
565

566
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
567
    if (dispatcherPtr) {
2,060✔
568
        HSM_TRACE_DEBUG("state=<%s>", getStateName(mInitialState).c_str());
2,060✔
569
        std::list<StateID_t> entryPoints;
2,060✔
570

571
        (void)onStateEntering(mInitialState, VariantVector_t());
2,060✔
572
        mActiveStates.push_back(mInitialState);
2,060✔
573
        onStateChanged(mInitialState, VariantVector_t());
2,060✔
574

575
        if (true == getEntryPoints(mInitialState, INVALID_HSM_EVENT_ID, VariantVector_t(), entryPoints)) {
2,060✔
576
            PendingEventInfo entryPointTransitionEvent;
240✔
577

578
            entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
240✔
579
            entryPointTransitionEvent.type = INVALID_HSM_EVENT_ID;
240✔
580

581
            {
240✔
582
                HSM_SYNC_EVENTS_QUEUE();
240✔
583
                mPendingEvents.push_front(entryPointTransitionEvent);
240✔
584
            }
240✔
585
        }
240✔
586

587
        if (false == mPendingEvents.empty()) {
2,060✔
588
            dispatcherPtr->emitEvent(mEventsHandlerId);
240✔
589
        }
590
    }
2,060✔
591
}
2,060✔
592

593
void HierarchicalStateMachine::Impl::transitionSimple(const EventID_t event) {
140✔
594
    (void)transitionExWithArgsArray(event, false, false, 0, VariantVector_t());
140✔
595
}
140✔
596

597
void HierarchicalStateMachine::Impl::dispatchEvents() {
1,289,551✔
598
    HSM_TRACE_CALL_DEBUG_ARGS("mPendingEvents.size=%ld", mPendingEvents.size());
1,289,551✔
599
    auto dispatcherPtr = mDispatcher.lock();
1,289,551✔
600

601
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
602
    if (dispatcherPtr) {
1,289,551✔
603
        if (false == mStopDispatching) {
1,289,551✔
604
            if (false == mPendingEvents.empty()) {
1,289,543✔
605
                PendingEventInfo pendingEvent;
695,446✔
606

607
                {
695,446✔
608
                    HSM_SYNC_EVENTS_QUEUE();
695,446✔
609
                    pendingEvent = mPendingEvents.front();
695,446✔
610
                    mPendingEvents.pop_front();
695,446✔
611
                }
695,446✔
612

613
                HsmEventStatus_t transitiontStatus = doTransition(pendingEvent);
695,446✔
614

615
                HSM_TRACE_DEBUG("unlock with status %d", SC2INT(transitiontStatus));
695,446✔
616
                pendingEvent.unlock(transitiontStatus);
695,446✔
617
            }
695,446✔
618

619
            if ((false == mStopDispatching) && (false == mPendingEvents.empty())) {
1,289,543✔
620
                dispatcherPtr->emitEvent(mEventsHandlerId);
687,619✔
621
            }
622
        }
623
    }
624
}
1,289,551✔
625

626
void HierarchicalStateMachine::Impl::dispatchTimerEvent(const TimerID_t id) {
130✔
627
    HSM_TRACE_CALL_DEBUG_ARGS("id=%d", SC2INT(id));
130✔
628
    auto it = mTimers.find(id);
130✔
629

630
    if (mTimers.end() != it) {
130✔
631
        transitionSimple(it->second);
130✔
632
    }
633
}
130✔
634

635
bool HierarchicalStateMachine::Impl::onStateExiting(const StateID_t state) {
694,926✔
636
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
694,926✔
637
    bool res = true;
694,926✔
638
    auto it = mRegisteredStates.find(state);
694,926✔
639

640
    if ((mRegisteredStates.end() != it) && it->second.onExiting) {
694,926✔
641
        res = it->second.onExiting();
330✔
642
        logHsmAction(HsmLogAction::CALLBACK_EXIT,
330✔
643
                     state,
644
                     INVALID_HSM_STATE_ID,
645
                     INVALID_HSM_EVENT_ID,
646
                     (false == res),
647
                     VariantVector_t());
330✔
648
    }
649

650
    // execute state action only if transition was accepted by client
651
    if (true == res) {
330✔
652
        executeStateAction(state, StateActionTrigger::ON_STATE_EXIT);
694,906✔
653
    }
654

655
    return res;
694,926✔
656
}
657

658
bool HierarchicalStateMachine::Impl::onStateEntering(const StateID_t state, const VariantVector_t& args) {
697,676✔
659
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
697,676✔
660
    bool res = true;
697,676✔
661

662
    // since we can have a situation when same state is entered twice (parallel transitions) there
663
    // is no need to call callbacks multiple times
664
    if (false == isStateActive(state)) {
697,676✔
665
        auto it = mRegisteredStates.find(state);
697,626✔
666

667
        if ((mRegisteredStates.end() != it) && it->second.onEntering) {
697,626✔
668
            res = it->second.onEntering(args);
560✔
669
            logHsmAction(HsmLogAction::CALLBACK_ENTER, INVALID_HSM_STATE_ID, state, INVALID_HSM_EVENT_ID, (false == res), args);
560✔
670
        }
671

672
        // execute state action only if transition was accepted by client
673
        if (true == res) {
560✔
674
            executeStateAction(state, StateActionTrigger::ON_STATE_ENTRY);
697,606✔
675
        }
676
    }
677

678
    return res;
697,676✔
679
}
680

681
void HierarchicalStateMachine::Impl::onStateChanged(const StateID_t state, const VariantVector_t& args) {
697,606✔
682
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
697,606✔
683
    auto it = mRegisteredStates.find(state);
697,606✔
684

685
    if ((mRegisteredStates.end() != it) && it->second.onStateChanged) {
697,606✔
686
        it->second.onStateChanged(args);
695,986✔
687
        logHsmAction(HsmLogAction::CALLBACK_STATE, INVALID_HSM_STATE_ID, state, INVALID_HSM_EVENT_ID, false, args);
695,986✔
688
    } else {
689
        HSM_TRACE_WARNING("no callback registered for state <%s>", getStateName(state).c_str());
697,606✔
690
    }
691
}
697,606✔
692

693
void HierarchicalStateMachine::Impl::executeStateAction(const StateID_t state, const StateActionTrigger actionTrigger) {
1,392,512✔
694
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>, actionTrigger=%d", getStateName(state).c_str(), SC2INT(actionTrigger));
1,392,512✔
695
    auto dispatcherPtr = mDispatcher.lock();
1,392,512✔
696

697
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shared_ptr has a bool() operator
698
    if (dispatcherPtr) {
1,392,512✔
699
        auto key = std::make_pair(state, actionTrigger);
1,392,484✔
700
        auto itRange = mRegisteredActions.equal_range(key);
1,392,484✔
701

702
        if (itRange.first != itRange.second) {
1,392,484✔
703
            switch (actionTrigger) {
180✔
704
                case StateActionTrigger::ON_STATE_ENTRY:
130✔
705
                    logHsmAction(HsmLogAction::ON_ENTER_ACTIONS, INVALID_HSM_STATE_ID, state);
130✔
706
                    break;
130✔
707
                case StateActionTrigger::ON_STATE_EXIT:
50✔
708
                    logHsmAction(HsmLogAction::ON_EXIT_ACTIONS, INVALID_HSM_STATE_ID, state);
50✔
709
                    break;
50✔
710
                default:
711
                    // NOTE: do nothing
712
                    break;
713
            }
714

715
            for (auto it = itRange.first; it != itRange.second; ++it) {
410✔
716
                const StateActionInfo& actionInfo = it->second;
230✔
717

718
                if (StateAction::START_TIMER == actionInfo.action) {
230✔
719
                    dispatcherPtr->startTimer(mTimerHandlerId,
220✔
720
                                              actionInfo.actionArgs[0].toInt64(),
110✔
721
                                              actionInfo.actionArgs[1].toInt64(),
110✔
722
                                              actionInfo.actionArgs[2].toBool());
110✔
723
                } else if (StateAction::STOP_TIMER == actionInfo.action) {
120✔
724
                    dispatcherPtr->stopTimer(actionInfo.actionArgs[0].toInt64());
10✔
725
                } else if (StateAction::RESTART_TIMER == actionInfo.action) {
110✔
726
                    dispatcherPtr->restartTimer(actionInfo.actionArgs[0].toInt64());
10✔
727
                } else if (StateAction::TRANSITION == actionInfo.action) {
100✔
728
                    VariantVector_t transitionArgs;
100✔
729

730
                    if (actionInfo.actionArgs.size() > 1u) {
100✔
731
                        transitionArgs.reserve(actionInfo.actionArgs.size() - 1u);
10✔
732

733
                        for (size_t i = 1; i < actionInfo.actionArgs.size(); ++i) {
30✔
734
                            transitionArgs.push_back(actionInfo.actionArgs[i]);
20✔
735
                        }
736
                    }
737

738
                    transitionWithArgsArray(static_cast<EventID_t>(actionInfo.actionArgs[0].toInt64()), transitionArgs);
100✔
739
                } else {
100✔
740
                    HSM_TRACE_WARNING("unsupported action <%d>", SC2INT(actionInfo.action));
230✔
741
                }
742
            }
743
        }
744
    }
745
}
1,392,512✔
746

747
bool HierarchicalStateMachine::Impl::getParentState(const StateID_t child, StateID_t& outParent) {
1,395,862✔
748
    bool wasFound = false;
1,395,862✔
749
    auto it = std::find_if(mSubstates.begin(), mSubstates.end(), [child](const std::pair<StateID_t, StateID_t>& item) {
1,395,862✔
750
        // cppcheck-suppress misra-c2012-15.5 ; false-positive. "return" statement belongs to lambda function
751
        return (child == item.second);
22,940✔
752
    });
753

754
    if (mSubstates.end() != it) {
1,395,862✔
755
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
5,580✔
756
        wasFound = true;
5,580✔
757
    }
758

759
    return wasFound;
1,395,862✔
760
}
761
bool HierarchicalStateMachine::Impl::isSubstateOf(const StateID_t parent, const StateID_t child) {
698,196✔
762
    HSM_TRACE_CALL_DEBUG_ARGS("parent=<%s>, child=<%s>", getStateName(parent).c_str(), getStateName(child).c_str());
698,196✔
763
    StateID_t curState = child;
698,196✔
764

765
    if (parent != child) {
698,196✔
766
        // TODO: can be optimized by checking siblings on each level
767

768
        do {
699,366✔
769
            if (false == getParentState(curState, curState)) {
699,366✔
770
                break;
771
            }
772
        } while (parent != curState);
3,890✔
773
    }
774

775
    return (parent != child) && (parent == curState);
698,196✔
776
}
777

778
bool HierarchicalStateMachine::Impl::isFinalState(const StateID_t state) const {
510✔
779
    return (mFinalStates.end() != mFinalStates.find(state));
510✔
780
}
781

782
bool HierarchicalStateMachine::Impl::hasActiveChildren(const StateID_t parent, const bool includeFinal) {
240✔
783
    HSM_TRACE_CALL_DEBUG_ARGS("parent=<%s>", getStateName(parent).c_str());
240✔
784
    bool res = false;
240✔
785

786
    for (const StateID_t activeStateId: mActiveStates) {
700✔
787
        if ((true == includeFinal) || (false == isFinalState(activeStateId))) {
510✔
788
            if (isSubstateOf(parent, activeStateId)) {
290✔
789
                HSM_TRACE_DEBUG("parent=<%s> has <%s> active", getStateName(parent).c_str(), getStateName(activeStateId).c_str());
790
                res = true;
791
                break;
792
            }
793
        }
794
    }
795

796
    HSM_TRACE_CALL_RESULT("res=%d", BOOL2INT(res));
240✔
797
    return res;
240✔
798
}
799

800
bool HierarchicalStateMachine::Impl::getHistoryParent(const StateID_t historyState, StateID_t& outParent) {
100✔
801
    bool wasFound = false;
100✔
802
    auto it =
100✔
803
        std::find_if(mHistoryStates.begin(), mHistoryStates.end(), [historyState](const std::pair<StateID_t, StateID_t>& item) {
100✔
804
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
805
            // cppcheck-suppress misra-c2012-15.5
806
            return (historyState == item.second);
100✔
807
        });
808

809
    if (mHistoryStates.end() != it) {
100✔
810
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
100✔
811
        wasFound = true;
100✔
812
    }
813

814
    return wasFound;
100✔
815
}
816

817
void HierarchicalStateMachine::Impl::updateHistory(const StateID_t topLevelState, const std::list<StateID_t>& exitedStates) {
694,516✔
818
    HSM_TRACE_CALL_DEBUG_ARGS("topLevelState=<%s>, exitedStates.size=%ld",
819
                              getStateName(topLevelState).c_str(),
820
                              exitedStates.size());
694,516✔
821

822
    std::list<std::list<StateID_t>*> upatedHistory;
694,516✔
823

824
    for (auto itActiveState = exitedStates.begin(); itActiveState != exitedStates.end(); ++itActiveState) {
1,389,582✔
825
        StateID_t curState = *itActiveState;
695,066✔
826
        StateID_t parentState;
695,066✔
827

828
        while (true == getParentState(curState, parentState)) {
695,886✔
829
            HSM_TRACE_DEBUG("curState=<%s>, parentState=<%s>",
830
                            getStateName(curState).c_str(),
831
                            getStateName(parentState).c_str());
1,370✔
832
            auto itRange = mHistoryStates.equal_range(parentState);
1,370✔
833

834
            if (itRange.first != itRange.second) {
1,370✔
835
                HSM_TRACE_DEBUG("parent=<%s> has history items", getStateName(parentState).c_str());
836

837
                for (auto it = itRange.first; it != itRange.second; ++it) {
680✔
838
                    auto itCurHistory = mHistoryData.find(it->second);
340✔
839

840
                    if (itCurHistory != mHistoryData.end()) {
340✔
841
                        auto itUpdatedHistory =
340✔
842
                            std::find(upatedHistory.begin(), upatedHistory.end(), &itCurHistory->second.previousActiveStates);
340✔
843

844
                        if (itUpdatedHistory == upatedHistory.end()) {
340✔
845
                            itCurHistory->second.previousActiveStates.clear();
200✔
846
                            upatedHistory.push_back(&(itCurHistory->second.previousActiveStates));
200✔
847
                        } else {
848
                        }
849

850
                        if (HistoryType::SHALLOW == itCurHistory->second.type) {
340✔
851
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
160✔
852
                                          itCurHistory->second.previousActiveStates.end(),
160✔
853
                                          curState) == itCurHistory->second.previousActiveStates.end()) {
320✔
854
                                HSM_TRACE_DEBUG("SHALLOW -> store state <%s> in history of parent <%s>",
855
                                                getStateName(curState).c_str(),
856
                                                getStateName(it->second).c_str());
120✔
857
                                itCurHistory->second.previousActiveStates.push_back(curState);
120✔
858
                            }
859
                        } else if (HistoryType::DEEP == itCurHistory->second.type) {
180✔
860
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
180✔
861
                                          itCurHistory->second.previousActiveStates.end(),
180✔
862
                                          *itActiveState) == itCurHistory->second.previousActiveStates.end()) {
360✔
863
                                HSM_TRACE_DEBUG("DEEP -> store state <%s> in history of parent <%s>",
864
                                                getStateName(*itActiveState).c_str(),
865
                                                getStateName(it->second).c_str());
180✔
866
                                itCurHistory->second.previousActiveStates.push_back(*itActiveState);
340✔
867
                            }
868
                        } else {
869
                            // NOTE: do nothing
870
                        }
871
                    }
872
                }
873
            }
874

875
            if (topLevelState != parentState) {
1,370✔
876
                curState = parentState;
820✔
877
            } else {
878
                break;
879
            }
880
        }
881
    }
882
}
694,516✔
883

884
bool HierarchicalStateMachine::Impl::checkTransitionPossibility(const StateID_t fromState,
60✔
885
                                                                const EventID_t event,
886
                                                                const VariantVector_t& args) {
887
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
888

889
    StateID_t currentState = fromState;
60✔
890
    std::list<TransitionInfo> possibleTransitions;
60✔
891
    EventID_t nextEvent;
60✔
892
    bool possible = true;
60✔
893

894
    {
60✔
895
        // NOTE: findTransitionTarget can be a bit heavy. possible optimization to reduce lock time is
896
        //       to make a copy of mPendingEvents and work with it
897
        HSM_SYNC_EVENTS_QUEUE();
60✔
898

899
        for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()) && (true == possible); ++it) {
60✔
900
            nextEvent = it->type;
×
901
            possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
×
902

903
            if (true == possible) {
×
904
                if (false == possibleTransitions.empty()) {
×
905
                    currentState = possibleTransitions.front().destinationState;
×
906
                } else {
907
                    possible = false;
908
                    break;
909
                }
910
            }
911
        }
912
    }
60✔
913

914
    if (true == possible) {
60✔
915
        nextEvent = event;
60✔
916
        possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
60✔
917
    }
918

919
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
920
    return possible;
60✔
921
}
60✔
922

923
bool HierarchicalStateMachine::Impl::findTransitionTarget(const StateID_t fromState,
695,296✔
924
                                                          const EventID_t event,
925
                                                          const VariantVector_t& transitionArgs,
926
                                                          const bool searchParents,
927
                                                          std::list<TransitionInfo>& outTransitions) {
928
    HSM_TRACE_CALL_DEBUG_ARGS("fromState=<%s>, event=<%s>", getStateName(fromState).c_str(), getEventName(event).c_str());
695,296✔
929
    bool continueSearch;
695,296✔
930
    StateID_t curState = fromState;
695,296✔
931

932
    do {
695,296✔
933
        auto key = std::make_pair(curState, event);
695,296✔
934
        auto itRange = mTransitionsByEvent.equal_range(key);
695,296✔
935

936
        continueSearch = false;
695,296✔
937

938
        if (itRange.first == itRange.second) {
695,296✔
939
            if (true == searchParents) {
800✔
940
                StateID_t parentState;
40✔
941
                bool hasParent = getParentState(curState, parentState);
40✔
942

943
                if (true == hasParent) {
40✔
944
                    curState = parentState;
×
945
                    continueSearch = true;
×
946
                }
947
            }
948
        } else {
949
            for (auto it = itRange.first; it != itRange.second; ++it) {
1,389,252✔
950
                HSM_TRACE_DEBUG("check transition to <%s>...", getStateName(it->second.destinationState).c_str());
694,756✔
951

952
                if ((nullptr == it->second.checkCondition) ||
694,756✔
953
                    (it->second.expectedConditionValue == it->second.checkCondition(transitionArgs))) {
140✔
954
                    bool wasFound = false;
694,666✔
955
                    std::list<StateID_t> parentStates = {it->second.destinationState};
694,666✔
956

957
                    // cppcheck-suppress misra-c2012-15.4
958
                    do {
695,086✔
959
                        StateID_t currentParent = parentStates.front();
695,086✔
960

961
                        parentStates.pop_front();
695,086✔
962

963
                        // if state has substates we must check if transition into them is possible (after cond)
964
                        if (true == hasSubstates(currentParent)) {
695,086✔
965
                            if (true == hasEntryPoint(currentParent)) {
470✔
966
                                HSM_TRACE_DEBUG("state <%s> has entrypoints", getStateName(currentParent).c_str());
440✔
967
                                std::list<StateID_t> entryPoints;
440✔
968

969
                                if (true == getEntryPoints(currentParent, event, transitionArgs, entryPoints)) {
440✔
970
                                    parentStates.splice(parentStates.end(), entryPoints);
840✔
971
                                } else {
972
                                    HSM_TRACE_WARNING("no matching entrypoints found");
973
                                    break;
974
                                }
975
                            } else {
20✔
976
                                HSM_TRACE_WARNING("state <%s> doesn't have an entrypoint defined",
977
                                                  getStateName(currentParent).c_str());
978
                                break;
979
                            }
980
                        } else {
981
                            outTransitions.push_back(it->second);
694,666✔
982
                            wasFound = true;
983
                        }
984
                    } while ((false == wasFound) && (parentStates.empty() == false));
420✔
985
                }
694,666✔
986
            }
987
        }
988
    } while (true == continueSearch);
695,296✔
989

990
    HSM_TRACE_CALL_RESULT("%s", BOOL2STR(outTransitions.empty() == false));
695,296✔
991
    return (outTransitions.empty() == false);
695,296✔
992
}
993

994
typename HsmEventStatus_t HierarchicalStateMachine::Impl::doTransition(const PendingEventInfo& event) {
695,446✔
995
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, transitionType=%d", getEventName(event.type).c_str(), SC2INT(event.transitionType));
695,446✔
996
    HsmEventStatus_t res = HsmEventStatus_t::DONE_FAILED;
695,446✔
997
    auto activeStatesSnapshot = mActiveStates;
695,446✔
998
    std::list<StateID_t> acceptedStates;  // list of states that accepted transitions
695,446✔
999

1000
    for (auto it = activeStatesSnapshot.rbegin(); it != activeStatesSnapshot.rend(); ++it) {
1,392,462✔
1001
        // in case of parallel transitions some states might become inactive after handleSingleTransition()
1002
        // example: [*B, *C] -> D
1003
        if (true == isStateActive(*it)) {
697,016✔
1004
            // we don't need to process transitions for active states if their child already processed it
1005
            bool childStateProcessed = false;
697,016✔
1006

1007
            for (const auto& state : acceptedStates) {
697,086✔
1008
                if (true == isSubstateOf(*it, state)) {
880✔
1009
                    childStateProcessed = true;
1010
                    break;
1011
                }
1012
            }
1013

1014
            if (false == childStateProcessed) {
697,016✔
1015
                const HsmEventStatus_t singleTransitionResult = handleSingleTransition(*it, event);
696,206✔
1016

1017
                switch (singleTransitionResult) {
696,206✔
1018
                    case HsmEventStatus_t::PENDING:
700✔
1019
                        res = singleTransitionResult;
700✔
1020
                        acceptedStates.push_back(*it);
700✔
1021
                        break;
1022
                    case HsmEventStatus_t::DONE_OK:
694,616✔
1023
                        logHsmAction(HsmLogAction::IDLE,
694,616✔
1024
                                     INVALID_HSM_STATE_ID,
1025
                                     INVALID_HSM_STATE_ID,
1026
                                     INVALID_HSM_EVENT_ID,
1027
                                     false,
1028
                                     VariantVector_t());
694,616✔
1029
                        if (HsmEventStatus_t::PENDING != res) {
694,616✔
1030
                            res = singleTransitionResult;
694,616✔
1031
                        }
1032
                        acceptedStates.push_back(*it);
697,016✔
1033
                        break;
1034
                    case HsmEventStatus_t::CANCELED:
1035
                    case HsmEventStatus_t::DONE_FAILED:
1036
                    default:
1037
                        // do nothing
1038
                        break;
1039
                }
1040
            }
1041
        }
1042
    }
1043

1044
    if (mFailedTransitionCallback && ((HsmEventStatus_t::DONE_FAILED == res) || (HsmEventStatus_t::CANCELED == res))) {
695,446✔
1045
        mFailedTransitionCallback(event.type, event.args);
120✔
1046
    }
1047

1048
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
695,446✔
1049
    return res;
695,446✔
1050
}
1,390,892✔
1051

1052
typename HsmEventStatus_t HierarchicalStateMachine::Impl::handleSingleTransition(const StateID_t activeState,
696,206✔
1053
                                                                                 const PendingEventInfo& event) {
1054
    HSM_TRACE_CALL_DEBUG_ARGS("activeState=<%s>, event=<%s>, transitionType=%d",
1055
                              getStateName(activeState).c_str(),
1056
                              getEventName(event.type).c_str(),
1057
                              SC2INT(event.transitionType));
696,206✔
1058
    HsmEventStatus_t res = HsmEventStatus_t::DONE_FAILED;
696,206✔
1059
    const StateID_t fromState = activeState;
696,206✔
1060
    bool isCorrectTransition = false;
696,206✔
1061
    std::list<TransitionInfo> matchingTransitions;
696,206✔
1062

1063
    DEBUG_DUMP_ACTIVE_STATES();
696,206✔
1064

1065
    // ========================================================
1066
    // determine target state based on current transition
1067
    if (TransitionBehavior::REGULAR == event.transitionType) {
696,206✔
1068
        isCorrectTransition = findTransitionTarget(fromState, event.type, event.args, false, matchingTransitions);
695,236✔
1069

1070
        if (false == isCorrectTransition) {
1071
            HSM_TRACE_WARNING("no suitable transition from state <%s> with event <%s>",
1072
                              getStateName(fromState).c_str(),
1073
                              getEventName(event.type).c_str());
1074
        }
1075
    } else if (TransitionBehavior::ENTRYPOINT == event.transitionType) {
970✔
1076
        isCorrectTransition = true;
720✔
1077

1078
        // if fromState doesnt have active children
1079
        for (auto it = mActiveStates.rbegin(); it != mActiveStates.rend(); ++it) {
1,760✔
1080
            if (fromState != *it) {
1,040✔
1081
                StateID_t activeParent = INVALID_HSM_STATE_ID;
320✔
1082

1083
                if (true == getParentState(*it, activeParent)) {
320✔
1084
                    if (activeParent == fromState) {
80✔
1085
                        // no need to handle entry transition for already active state
1086
                        isCorrectTransition = false;
×
1087
                        break;
×
1088
                    }
1089
                }
1090
            }
1091
        }
1092

1093
        if (true == isCorrectTransition) {
×
1094
            std::list<StateID_t> entryStates;
720✔
1095

1096
            isCorrectTransition = getEntryPoints(fromState, event.type, event.args, entryStates);
720✔
1097

1098
            if (true == isCorrectTransition) {
720✔
1099
                for (auto it = entryStates.begin(); it != entryStates.end(); ++it) {
1,470✔
1100
                    (void)matchingTransitions.emplace_back(
1,560✔
1101
                        TransitionInfo{fromState, *it, TransitionType::EXTERNAL_TRANSITION, nullptr, nullptr});
1,560✔
1102
                }
1103
            } else {
1104
                HSM_TRACE_WARNING("state <%s> doesn't have a suitable entry point (event <%s>)",
1105
                                  getStateName(fromState).c_str(),
1106
                                  getEventName(event.type).c_str());
720✔
1107
            }
1108
        }
720✔
1109
    } else if (TransitionBehavior::FORCED == event.transitionType) {
250✔
1110
        HSM_TRACE_DEBUG("forced history transitions: %d", SC2INT(event.forcedTransitionsInfo->size()));
250✔
1111
        matchingTransitions = *event.forcedTransitionsInfo;
250✔
1112
        isCorrectTransition = true;
1113
    } else {
1114
        // NOTE: do nothing
1115
    }
1116

1117
    // ========================================================
1118
    // handle transition if it passed validation and has a target state
1119
    if (true == isCorrectTransition) {
695,956✔
1120
        bool isExitAllowed = true;
695,346✔
1121
        std::list<StateID_t> exitedStates;
695,346✔
1122

1123
        // execute self transitions first
1124
        for (auto it = matchingTransitions.begin(); it != matchingTransitions.end(); ++it) {
1,391,012✔
1125
            if ((it->fromState == it->destinationState) && (TransitionType::INTERNAL_TRANSITION == it->transitionType)) {
695,666✔
1126
                // TODO: separate type for self transition?
1127
                logHsmAction(HsmLogAction::TRANSITION, it->fromState, it->destinationState, event.type, false, event.args);
60✔
1128

1129
                // NOTE: false-positive. std::function has a bool() operator
1130
                // cppcheck-suppress misra-c2012-14.4
1131
                if (it->onTransition) {
60✔
1132
                    it->onTransition(event.args);
695,666✔
1133
                }
1134

1135
                res = HsmEventStatus_t::DONE_OK;
1136
            }
1137
        }
1138

1139
        // execute exit transition (only once in case of parallel transitions)
1140
        for (auto it = matchingTransitions.begin(); it != matchingTransitions.end(); ++it) {
1,391,012✔
1141
            // everything except internal self-transitions
1142
            if ((it->fromState != it->destinationState) || (TransitionType::EXTERNAL_TRANSITION == it->transitionType)) {
695,666✔
1143
                // exit active states only during regular transitions
1144
                if (TransitionBehavior::REGULAR == event.transitionType) {
695,606✔
1145
                    // it's an outer transition from parent state. we need to find and exit all active substates
1146
                    for (auto itActiveState = mActiveStates.rbegin(); itActiveState != mActiveStates.rend(); ++itActiveState) {
1,390,242✔
1147
                        HSM_TRACE_DEBUG("OUTER EXIT: FROM=%s, ACTIVE=%s",
1148
                                        getStateName(it->fromState).c_str(),
1149
                                        getStateName(*itActiveState).c_str());
695,726✔
1150
                        if ((it->fromState == *itActiveState) || (true == isSubstateOf(it->fromState, *itActiveState))) {
695,726✔
1151
                            isExitAllowed = onStateExiting(*itActiveState);
694,926✔
1152

1153
                            if (true == isExitAllowed) {
694,926✔
1154
                                exitedStates.push_back(*itActiveState);
695,706✔
1155
                            } else {
1156
                                break;
1157
                            }
1158
                        }
1159
                    }
1160

1161
                    // if no one blocked ongoing transition - remove child states from active list
1162
                    if (true == isExitAllowed) {
694,536✔
1163
                        // store history for states between "fromState" ----> "it->fromState"
1164
                        updateHistory(it->fromState, exitedStates);
694,516✔
1165

1166
                        for (auto itState = exitedStates.begin(); itState != exitedStates.end(); ++itState) {
1,389,582✔
1167
                            mActiveStates.remove(*itState);
695,066✔
1168
                        }
1169
                    }
1170
                    // if one of the states blocked ongoing transition we need to rollback
1171
                    else {
1172
                        for (auto itState = exitedStates.begin(); itState != exitedStates.end(); ++itState) {
30✔
1173
                            mActiveStates.remove(*itState);
10✔
1174
                            // to prevent infinite loops we don't allow state to cancel transition
1175
                            (void)onStateEntering(*itState, VariantVector_t());
10✔
1176
                            mActiveStates.push_back(*itState);
10✔
1177
                            onStateChanged(*itState, VariantVector_t());
10✔
1178
                        }
1179
                    }
1180
                }
1181
            }
1182
        }
1183

1184
        // proceed if transition was not blocked during state exit
1185
        if (true == isExitAllowed) {
695,346✔
1186
            for (auto it = matchingTransitions.begin(); it != matchingTransitions.end(); ++it) {
1,390,972✔
1187
                // everything except internal self-transitions
1188
                if ((it->fromState != it->destinationState) || (TransitionType::EXTERNAL_TRANSITION == it->transitionType)) {
695,646✔
1189
                    // NOTE: Decide if we need functionality to cancel ongoing transition
1190
                    logHsmAction(
695,586✔
1191
                        ((TransitionBehavior::ENTRYPOINT != event.transitionType) ? HsmLogAction::TRANSITION
695,586✔
1192
                                                                                  : HsmLogAction::TRANSITION_ENTRYPOINT),
1193
                        it->fromState,
695,586✔
1194
                        it->destinationState,
695,586✔
1195
                        event.type,
695,586✔
1196
                        false,
1197
                        event.args);
695,586✔
1198

1199
                    // NOTE: false-positive. std::shared_ptr has a bool() operator
1200
                    // cppcheck-suppress misra-c2012-14.4
1201
                    if (it->onTransition) {
695,586✔
1202
                        it->onTransition(event.args);
260✔
1203
                    }
1204

1205
                    if (true == onStateEntering(it->destinationState, event.args)) {
695,586✔
1206
                        std::list<StateID_t> entryPoints;
695,566✔
1207

1208
                        if (true == replaceActiveState(fromState, it->destinationState)) {
695,566✔
1209
                            onStateChanged(it->destinationState, event.args);
695,516✔
1210
                        }
1211

1212
                        // check if current state is a final state
1213
                        const auto itFinalStateEvent = mFinalStates.find(it->destinationState);
695,566✔
1214

1215
                        if (itFinalStateEvent != mFinalStates.end()) {
695,566✔
1216
                            StateID_t parentState = INVALID_HSM_STATE_ID;
250✔
1217

1218
                            // don't generate events for top level final states since no one can process them
1219
                            if (true == getParentState(it->destinationState, parentState)) {
250✔
1220
                                // check if there are any other active siblings in this parent state
1221
                                // only generate final state event when all siblings got deactivated
1222
                                if (false == hasActiveChildren(parentState, false)) {
240✔
1223
                                    PendingEventInfo finalStateEvent;
190✔
1224

1225
                                    finalStateEvent.transitionType = TransitionBehavior::REGULAR;
190✔
1226
                                    finalStateEvent.args = event.args;
190✔
1227

1228
                                    if (INVALID_HSM_EVENT_ID != itFinalStateEvent->second) {
190✔
1229
                                        finalStateEvent.type = itFinalStateEvent->second;
110✔
1230
                                    } else {
1231
                                        finalStateEvent.type = event.type;
80✔
1232
                                    }
1233

1234
                                    {
190✔
1235
                                        HSM_SYNC_EVENTS_QUEUE();
190✔
1236
                                        mPendingEvents.push_front(finalStateEvent);
190✔
1237
                                    }
190✔
1238
                                }
190✔
1239
                            }
1240

1241
                            res = HsmEventStatus_t::DONE_OK;
250✔
1242
                        } else {
1243
                            // check if we transitioned into history state
1244
                            auto itHistoryData = mHistoryData.find(it->destinationState);
695,316✔
1245

1246
                            if (itHistoryData != mHistoryData.end()) {
695,316✔
1247
                                HSM_TRACE_DEBUG("state=<%s> is a history state with %ld stored states",
1248
                                                getStateName(it->destinationState).c_str(),
1249
                                                itHistoryData->second.previousActiveStates.size());
100✔
1250

1251
                                // transition to previous states
1252
                                if (itHistoryData->second.previousActiveStates.empty() == false) {
100✔
1253
                                    PendingEventInfo historyTransitionEvent = event;
60✔
1254

1255
                                    historyTransitionEvent.transitionType = TransitionBehavior::FORCED;
60✔
1256
                                    historyTransitionEvent.forcedTransitionsInfo =
60✔
1257
                                        std::make_shared<std::list<TransitionInfo>>();
60✔
1258

1259
                                    auto itPrevChildState = itHistoryData->second.previousActiveStates.end();
60✔
1260

1261
                                    {
60✔
1262
                                        HSM_SYNC_EVENTS_QUEUE();
60✔
1263

1264
                                        for (auto itPrevState = itHistoryData->second.previousActiveStates.begin();
60✔
1265
                                             itPrevState != itHistoryData->second.previousActiveStates.end();
220✔
1266
                                             ++itPrevState) {
220✔
1267
                                            if ((itPrevChildState != itHistoryData->second.previousActiveStates.end()) &&
260✔
1268
                                                (true == isSubstateOf(*itPrevState, *itPrevChildState))) {
100✔
1269
                                                if (false == historyTransitionEvent.forcedTransitionsInfo->empty()) {
60✔
1270
                                                    mPendingEvents.push_front(historyTransitionEvent);
60✔
1271
                                                }
1272

1273
                                                historyTransitionEvent.forcedTransitionsInfo =
60✔
1274
                                                    std::make_shared<std::list<TransitionInfo>>();
60✔
1275
                                                historyTransitionEvent.ignoreEntryPoints = true;
60✔
1276
                                            } else {
1277
                                                historyTransitionEvent.ignoreEntryPoints = false;
100✔
1278
                                            }
1279

1280
                                            itPrevChildState = itPrevState;
160✔
1281
                                            historyTransitionEvent.forcedTransitionsInfo->emplace_back(
160✔
1282
                                                it->destinationState,
160✔
1283
                                                *itPrevState,
160✔
1284
                                                TransitionType::EXTERNAL_TRANSITION,
320✔
1285
                                                nullptr,
320✔
1286
                                                nullptr);
320✔
1287
                                        }
1288

1289
                                        mPendingEvents.push_front(historyTransitionEvent);
60✔
1290
                                    }
60✔
1291

1292
                                    itHistoryData->second.previousActiveStates.clear();
60✔
1293

1294
                                    StateID_t historyParent;
60✔
1295

1296
                                    if (true == getHistoryParent(it->destinationState, historyParent)) {
60✔
1297
                                        historyTransitionEvent.forcedTransitionsInfo =
60✔
1298
                                            std::make_shared<std::list<TransitionInfo>>();
60✔
1299
                                        historyTransitionEvent.forcedTransitionsInfo->emplace_back(
60✔
1300
                                            it->destinationState,
60✔
1301
                                            historyParent,
1302
                                            TransitionType::EXTERNAL_TRANSITION,
120✔
1303
                                            nullptr,
120✔
1304
                                            nullptr);
60✔
1305
                                        historyTransitionEvent.ignoreEntryPoints = true;
60✔
1306

1307
                                        HSM_SYNC_EVENTS_QUEUE();
60✔
1308
                                        mPendingEvents.push_front(historyTransitionEvent);
60✔
1309
                                    }
60✔
1310
                                }
60✔
1311
                                // transition to default state or entry point
1312
                                else {
1313
                                    std::list<StateID_t> historyTargets;
40✔
1314
                                    StateID_t historyParent;
40✔
1315

1316
                                    if (true == getHistoryParent(it->destinationState, historyParent)) {
40✔
1317
                                        HSM_TRACE_DEBUG("found parent=<%s> for history state=<%s>",
1318
                                                        getStateName(historyParent).c_str(),
1319
                                                        getStateName(it->destinationState).c_str());
40✔
1320

1321
                                        if (INVALID_HSM_STATE_ID == itHistoryData->second.defaultTarget) {
40✔
1322
                                            // transition to parent's entry point if there is no default history target
1323
                                            historyTargets.push_back(historyParent);
10✔
1324
                                        } else {
1325
                                            historyTargets.push_back(itHistoryData->second.defaultTarget);
30✔
1326
                                            historyTargets.push_back(historyParent);
30✔
1327
                                        }
1328
                                    } else {
1329
                                        HSM_TRACE_ERROR("parent for history state=<%s> wasnt found",
1330
                                                        getStateName(it->destinationState).c_str());
40✔
1331
                                    }
1332

1333
                                    PendingEventInfo defHistoryTransitionEvent = event;
40✔
1334

1335
                                    defHistoryTransitionEvent.transitionType = TransitionBehavior::FORCED;
40✔
1336

1337
                                    for (const StateID_t historyTargetState : historyTargets) {
110✔
1338
                                        HsmTransitionCallback_t cbTransition;
70✔
1339

1340
                                        defHistoryTransitionEvent.forcedTransitionsInfo =
70✔
1341
                                            std::make_shared<std::list<TransitionInfo>>();
70✔
1342

1343
                                        if ((INVALID_HSM_STATE_ID != itHistoryData->second.defaultTarget) &&
70✔
1344
                                            (historyTargetState == historyParent)) {
60✔
1345
                                            defHistoryTransitionEvent.ignoreEntryPoints = true;
30✔
1346
                                        } else {
1347
                                            cbTransition = itHistoryData->second.defaultTargetTransitionCallback;
40✔
1348
                                        }
1349

1350
                                        defHistoryTransitionEvent.forcedTransitionsInfo->emplace_back(
70✔
1351
                                            it->destinationState,
70✔
1352
                                            historyTargetState,
1353
                                            TransitionType::EXTERNAL_TRANSITION,
140✔
1354
                                            cbTransition,
1355
                                            nullptr);
70✔
1356

1357
                                        mPendingEvents.push_front(defHistoryTransitionEvent);
70✔
1358
                                    }
70✔
1359
                                }
80✔
1360

1361
                                res = HsmEventStatus_t::PENDING;
1362
                            }
1363
                            // check if new state has substates and initiate entry transition
1364
                            else if ((false == event.ignoreEntryPoints) &&
1,390,282✔
1365
                                     (true == getEntryPoints(it->destinationState, event.type, event.args, entryPoints))) {
695,066✔
1366
                                HSM_TRACE_DEBUG("state <%s> has substates with %d entry points (first: <%s>)",
1367
                                                getStateName(it->destinationState).c_str(),
1368
                                                SC2INT(entryPoints.size()),
1369
                                                getStateName(entryPoints.front()).c_str());
450✔
1370
                                PendingEventInfo entryPointTransitionEvent = event;
450✔
1371

1372
                                entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
450✔
1373

1374
                                {
450✔
1375
                                    HSM_SYNC_EVENTS_QUEUE();
450✔
1376
                                    mPendingEvents.push_front(entryPointTransitionEvent);
450✔
1377
                                }
450✔
1378
                                res = HsmEventStatus_t::PENDING;
450✔
1379
                            } else {
450✔
1380
                                if (true == event.ignoreEntryPoints) {
694,766✔
1381
                                    HSM_TRACE_DEBUG(
1382
                                        "entry points were forcefully ignored (probably due to history transition)");
1383
                                    res = HsmEventStatus_t::PENDING;
1384
                                } else {
1385
                                    res = HsmEventStatus_t::DONE_OK;
694,616✔
1386
                                }
1387
                            }
1388
                        }
1389
                    } else {
695,566✔
1390
                        for (auto itState = exitedStates.begin(); itState != exitedStates.end(); ++itState) {
40✔
1391
                            // to prevent infinite loops we don't allow state to cancel transition
1392
                            (void)onStateEntering(*itState, VariantVector_t());
20✔
1393
                            (void)addActiveState(*itState);
20✔
1394
                            onStateChanged(*itState, VariantVector_t());
20✔
1395
                        }
1396
                    }
1397
                }
1398
            }
1399
        } else {
1400
            res = HsmEventStatus_t::CANCELED;
1401
        }
1402
    }
695,346✔
1403

1404
    if (HsmEventStatus_t::DONE_FAILED == res) {
696,206✔
1405
        HSM_TRACE_DEBUG("event <%s> in state <%s> was ignored.",
1406
                        getEventName(event.type).c_str(),
1407
                        getStateName(fromState).c_str());
1408
    }
1409

1410
    DEBUG_DUMP_ACTIVE_STATES();
696,206✔
1411
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
696,206✔
1412
    return res;
696,206✔
1413
}
696,206✔
1414

1415
void HierarchicalStateMachine::Impl::clearPendingEvents() {
10✔
1416
    HSM_TRACE_CALL_DEBUG_ARGS("clearPendingEvents: mPendingEvents.size()=%ld", mPendingEvents.size());
10✔
1417

1418
    for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()); ++it) {
10✔
1419
        // since ongoing transitions can't be canceled we need to treat entry point transitions as atomic
1420
        if (TransitionBehavior::REGULAR == it->transitionType) {
×
1421
            it->releaseLock();
×
1422
        }
1423
    }
1424

1425
    mPendingEvents.clear();
10✔
1426
}
10✔
1427

1428
// ============================================================================
1429
// PRIVATE: PendingEventInfo
1430
// ============================================================================
1431
HierarchicalStateMachine::Impl::PendingEventInfo::~PendingEventInfo() {
2,669,097✔
1432
    if (true == cvLock.unique()) {
2,670,247✔
1433
        HSM_TRACE_CALL_DEBUG_ARGS("event=<%d> was deleted. releasing lock", SC2INT(type));
1,150✔
1434
        unlock(HsmEventStatus_t::DONE_FAILED);
1,150✔
1435
        cvLock.reset();
1,150✔
1436
        syncProcessed.reset();
1,150✔
1437
    }
1438
}
2,683,587✔
1439

1440
void HierarchicalStateMachine::Impl::PendingEventInfo::initLock() {
1,150✔
1441
    if (!cvLock) {
1,150✔
1442
        cvLock = std::make_shared<Mutex>();
1,150✔
1443
        syncProcessed = std::make_shared<ConditionVariable>();
1,150✔
1444
        transitionStatus = std::make_shared<HsmEventStatus_t>();
1,150✔
1445
        *transitionStatus = HsmEventStatus_t::PENDING;
1,150✔
1446
    }
1447
}
1,150✔
1448

1449
void HierarchicalStateMachine::Impl::PendingEventInfo::releaseLock() {
×
1450
    if (true == isSync()) {
×
1451
        HSM_TRACE_CALL_DEBUG_ARGS("releaseLock");
×
1452
        unlock(HsmEventStatus_t::DONE_FAILED);
×
1453
        cvLock.reset();
×
1454
        syncProcessed.reset();
×
1455
    }
1456
}
×
1457

1458
bool HierarchicalStateMachine::Impl::PendingEventInfo::isSync() {
697,746✔
1459
    return (nullptr != cvLock);
697,746✔
1460
}
1461

1462
void HierarchicalStateMachine::Impl::PendingEventInfo::wait(const int timeoutMs) {
1,150✔
1463
    if (true == isSync()) {
1,150✔
1464
        // NOTE: lock is needed only because we have to use cond variable
1465
        UniqueLock lck(*cvLock);
1,150✔
1466

1467
        HSM_TRACE_CALL_DEBUG_ARGS("trying to wait... (current status=%d, %p)",
1468
                                  SC2INT(*transitionStatus),
1469
                                  transitionStatus.get());
1,150✔
1470
        if (timeoutMs > 0) {
1,150✔
1471
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
1472
            // cppcheck-suppress [misra-c2012-15.5, misra-c2012-17.7]
1473
            syncProcessed->wait_for(lck, timeoutMs, [=]() { return (HsmEventStatus_t::PENDING != *transitionStatus); });
2,300✔
1474
        } else {
1475
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
1476
            // cppcheck-suppress [misra-c2012-15.5, misra-c2012-17.7]
1477
            syncProcessed->wait(lck, [=]() { return (HsmEventStatus_t::PENDING != *transitionStatus); });
×
1478
        }
1479

1480
        HSM_TRACE_DEBUG("unlocked! transitionStatus=%d", SC2INT(*transitionStatus));
1,150✔
1481
    }
1,150✔
1482
}
1,150✔
1483

1484
void HierarchicalStateMachine::Impl::PendingEventInfo::unlock(const HsmEventStatus_t status) {
696,596✔
1485
    HSM_TRACE_CALL_DEBUG_ARGS("try to unlock with status=%d", SC2INT(status));
696,596✔
1486

1487
    if (true == isSync()) {
696,596✔
1488
        HSM_TRACE_DEBUG("SYNC object (%p)", transitionStatus.get());
2,980✔
1489
        *transitionStatus = status;
2,980✔
1490

1491
        if (status != HsmEventStatus_t::PENDING) {
2,980✔
1492
            syncProcessed->notify();
2,300✔
1493
        }
1494
    } else {
1495
        HSM_TRACE_DEBUG("ASYNC object");
696,596✔
1496
    }
1497
}
696,596✔
1498

1499
bool HierarchicalStateMachine::Impl::hasSubstates(const StateID_t parent) const {
695,086✔
1500
    return (mSubstates.find(parent) != mSubstates.end());
695,086✔
1501
}
1502

1503
bool HierarchicalStateMachine::Impl::hasEntryPoint(const StateID_t state) const {
470✔
1504
    return (mSubstateEntryPoints.find(state) != mSubstateEntryPoints.end());
470✔
1505
}
1506

1507
bool HierarchicalStateMachine::Impl::getEntryPoints(const StateID_t state,
698,286✔
1508
                                                    const EventID_t onEvent,
1509
                                                    const VariantVector_t& transitionArgs,
1510
                                                    std::list<StateID_t>& outEntryPoints) const {
1511
    auto itRange = mSubstateEntryPoints.equal_range(state);
698,286✔
1512

1513
    outEntryPoints.clear();
698,286✔
1514

1515
    for (auto it = itRange.first; it != itRange.second; ++it) {
700,546✔
1516
        if (((INVALID_HSM_EVENT_ID == it->second.onEvent) || (onEvent == it->second.onEvent)) &&
2,260✔
1517
            // check transition condition if it was defined
1518
            ((nullptr == it->second.checkCondition) ||
2,200✔
1519
             (it->second.checkCondition(transitionArgs) == it->second.expectedConditionValue))) {
120✔
1520
            outEntryPoints.push_back(it->second.state);
2,260✔
1521
        }
1522
    }
1523

1524
    return (false == outEntryPoints.empty());
698,286✔
1525
}
1526

1527
bool HierarchicalStateMachine::Impl::replaceActiveState(const StateID_t oldState, const StateID_t newState) {
695,566✔
1528
    HSM_TRACE_CALL_DEBUG_ARGS("oldState=<%s>, newState=<%s>", getStateName(oldState).c_str(), getStateName(newState).c_str());
695,566✔
1529

1530
    if (false == isSubstateOf(oldState, newState)) {
695,566✔
1531
        mActiveStates.remove(oldState);
694,596✔
1532
    }
1533

1534
    return addActiveState(newState);
695,566✔
1535
}
1536

1537
bool HierarchicalStateMachine::Impl::addActiveState(const StateID_t newState) {
695,586✔
1538
    HSM_TRACE_CALL_DEBUG_ARGS("newState=<%s>", getStateName(newState).c_str());
695,586✔
1539
    bool wasAdded = false;
695,586✔
1540

1541
    if (false == isStateActive(newState)) {
695,586✔
1542
        mActiveStates.push_back(newState);
695,536✔
1543
        wasAdded = true;
695,536✔
1544
    }
1545

1546
    HSM_TRACE_DEBUG("mActiveStates.size=%d", SC2INT(mActiveStates.size()));
695,586✔
1547
    return wasAdded;
695,586✔
1548
}
1549

1550
#ifdef HSM_ENABLE_SAFE_STRUCTURE
1551

1552
bool HierarchicalStateMachine::Impl::isTopState(const StateID_t state) const {
7,400✔
1553
    auto it = std::find(mTopLevelStates.begin(), mTopLevelStates.end(), state);
7,400✔
1554

1555
    return (it == mTopLevelStates.end());
7,400✔
1556
}
1557

1558
bool HierarchicalStateMachine::Impl::isSubstate(const StateID_t state) const {
5,730✔
1559
    bool result = false;
5,730✔
1560

1561
    for (auto itSubstate = mSubstates.begin(); itSubstate != mSubstates.end(); ++itSubstate) {
5,730✔
1562
        if (itSubstate->second == state) {
×
1563
            result = true;
1564
            break;
1565
        }
1566
    }
1567

1568
    return result;
5,730✔
1569
}
1570

1571
bool HierarchicalStateMachine::Impl::hasParentState(const StateID_t state, StateID_t& outParent) const {
3,850✔
1572
    bool hasParent = false;
3,850✔
1573

1574
    for (auto it = mSubstates.begin(); it != mSubstates.end(); ++it) {
8,110✔
1575
        if (state == it->second) {
4,740✔
1576
            hasParent = true;
480✔
1577
            outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
480✔
1578
            break;
480✔
1579
        }
1580
    }
1581

1582
    return hasParent;
3,850✔
1583
}
1584

1585
#endif  // HSM_ENABLE_SAFE_STRUCTURE
1586

1587
bool HierarchicalStateMachine::Impl::enableHsmDebugging() {
×
1588
#ifdef HSMBUILD_DEBUGGING
1589
    char* envPath = std::getenv(ENV_DUMPPATH);
×
1590

1591
    return enableHsmDebugging((nullptr == envPath) ? DEFAULT_DUMP_PATH : std::string(envPath));
×
1592
#else
1593
    return true;
1594
#endif
1595
}
1596

1597
bool HierarchicalStateMachine::Impl::enableHsmDebugging(const std::string& dumpPath) {
×
1598
#ifdef HSMBUILD_DEBUGGING
1599
    bool res = false;
×
1600
    bool isNewLog = (access(dumpPath.c_str(), F_OK) != 0);
×
1601

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

1605
        if (true == isNewLog) {
×
1606
            *mHsmLog << "---\n";
×
1607
            mHsmLog->flush();
×
1608
        }
1609

1610
        res = true;
1611
    }
1612

1613
    return res;
×
1614
#else
1615
    return true;
1616
#endif
1617
}
1618

1619
void HierarchicalStateMachine::Impl::disableHsmDebugging() {
4,100✔
1620
#ifdef HSMBUILD_DEBUGGING
1621
    mHsmLogFile.close();
4,100✔
1622
#endif
1623
}
4,100✔
1624

1625
void HierarchicalStateMachine::Impl::logHsmAction(const HsmLogAction action,
2,089,378✔
1626
                                                  const StateID_t fromState,
1627
                                                  const StateID_t targetState,
1628
                                                  const EventID_t event,
1629
                                                  const bool hasFailed,
1630
                                                  const VariantVector_t& args) {
1631
#ifdef HSMBUILD_DEBUGGING
1632
    if (true == mHsmLogFile.is_open()) {
2,089,378✔
1633
        static const std::map<HsmLogAction, std::string> actionsMap = {
×
1634
            std::make_pair(HsmLogAction::IDLE, "idle"),
×
1635
            std::make_pair(HsmLogAction::TRANSITION, "transition"),
×
1636
            std::make_pair(HsmLogAction::TRANSITION_ENTRYPOINT, "transition_entrypoint"),
×
1637
            std::make_pair(HsmLogAction::CALLBACK_EXIT, "callback_exit"),
×
1638
            std::make_pair(HsmLogAction::CALLBACK_ENTER, "callback_enter"),
×
1639
            std::make_pair(HsmLogAction::CALLBACK_STATE, "callback_state"),
×
1640
            std::make_pair(HsmLogAction::ON_ENTER_ACTIONS, "onenter_actions"),
×
1641
            std::make_pair(HsmLogAction::ON_EXIT_ACTIONS, "onexit_actions")};
×
1642
        char bufTime[80] = {0};
×
1643
        char bufTimeMs[6] = {0};
×
1644
        auto currentTimePoint = std::chrono::system_clock::now();
×
1645
        const std::time_t tt = std::chrono::system_clock::to_time_t(currentTimePoint);
×
1646
        std::tm timeinfo;
×
1647
        const std::tm* tmResult = nullptr;  // this is just to check that localtime was executed correctly
×
1648

1649
  #ifdef WIN32
1650
        if (0 == ::localtime_s(&timeinfo, &tt)) {
1651
            tmResult = &timeinfo;
1652
        }
1653
  #else
1654
        tmResult = localtime(&tt);
×
1655
        if (nullptr != tmResult) {
×
1656
            timeinfo = *tmResult;
×
1657
        }
1658
  #endif  // WIN32
1659

1660
        if (nullptr != tmResult) {
×
1661
            (void)std::strftime(bufTime, sizeof(bufTime), "%Y-%m-%d %H:%M:%S", &timeinfo);
×
1662
            (void)snprintf(
×
1663
                bufTimeMs,
1664
                sizeof(bufTimeMs),
1665
                ".%03d",
1666
                static_cast<int>(
1667
                    std::chrono::duration_cast<std::chrono::milliseconds>(currentTimePoint.time_since_epoch()).count() % 1000));
×
1668
        } else {
1669
            (void)std::strcpy(bufTime, "0000-00-00 00:00:00");
×
1670
            (void)std::strcpy(bufTimeMs, ".000");
×
1671
        }
1672

1673
        *mHsmLog << "\n-\n"
×
1674
                    "  timestamp: \""
1675
                 << bufTime << bufTimeMs
1676
                 << "\"\n"
1677
                    "  active_states:";
×
1678

1679
        for (auto itState = mActiveStates.begin(); itState != mActiveStates.end(); ++itState) {
×
1680
            *mHsmLog << "\n    - \"" << getStateName(*itState) << "\"";
×
1681
        }
1682

1683
        *mHsmLog << "\n  action: " << actionsMap.at(action)
×
1684
                 << "\n"
1685
                    "  from_state: \""
1686
                 << getStateName(fromState)
×
1687
                 << "\"\n"
1688
                    "  target_state: \""
1689
                 << getStateName(targetState)
×
1690
                 << "\"\n"
1691
                    "  event: \""
1692
                 << getEventName(event)
×
1693
                 << "\"\n"
1694
                    "  status: "
1695
                 << (hasFailed ? "failed" : "")
1696
                 << "\n"
1697
                    "  args:";
×
1698

1699
        for (auto itArg = args.begin(); itArg != args.end(); ++itArg) {
×
1700
            *mHsmLog << "\n    - " << itArg->toString();
×
1701
        }
1702

1703
        mHsmLog->flush();
×
1704
    }
1705
#endif  // HSMBUILD_DEBUGGING
1706
}
2,089,378✔
1707

1708
#ifndef HSM_DISABLE_DEBUG_TRACES
1709
void HierarchicalStateMachine::Impl::dumpActiveStates() {
1,392,412✔
1710
    HSM_TRACE_CALL();
1,392,412✔
1711

1712
    std::string temp;
1,392,412✔
1713

1714
    for (auto it = mActiveStates.begin(); it != mActiveStates.end(); ++it) {
2,791,624✔
1715
        temp += getStateName(*it) + std::string(", ");
2,798,424✔
1716
    }
1717

1718
    HSM_TRACE_DEBUG("active states: <%s>", temp.c_str());
1,392,412✔
1719
}
1,392,412✔
1720

1721
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
1,399,212✔
1722
    std::string res;
1,399,212✔
1723
  #ifndef HSM_DISABLE_THREADSAFETY
1724
    LockGuard lk(mParentSync);
1,399,212✔
1725
  #endif
1726

1727
    if (nullptr != mParent) {
1,399,212✔
1728
        res = mParent->getStateName(state);
1,398,884✔
1729
    }
1730

1731
    return res;
1,399,212✔
1732
}
1,399,212✔
1733

1734
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
×
1735
    std::string res;
×
1736
  #ifndef HSM_DISABLE_THREADSAFETY
1737
    LockGuard lk(mParentSync);
×
1738
  #endif
1739

1740
    if (nullptr != mParent) {
×
1741
        res = mParent->getEventName(event);
×
1742
    }
1743

1744
    return res;
×
1745
}
×
1746
#else   // HSM_DISABLE_DEBUG_TRACES
1747

1748
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
1749
    return std::string();
1750
}
1751

1752
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
1753
    return std::string();
1754
}
1755
#endif  // HSM_DISABLE_DEBUG_TRACES
1756

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