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

igor-krechetov / hsmcpp / 4509400661

pending completion
4509400661

push

github

igor-krechetov
[0.33.0][r] improvement to timers API

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

1921 of 2338 relevant lines covered (82.16%)

161238.04 hits per line

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

90.23
/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

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

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

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

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

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

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

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

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

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

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

66
namespace hsmcpp {
67

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

78
HierarchicalStateMachine::Impl::~Impl() {
3,980✔
79
    release();
1,990✔
80
}
8,170✔
81

82
void HierarchicalStateMachine::Impl::resetParent() {
1,990✔
83
#ifndef HSM_DISABLE_THREADSAFETY
84
    LockGuard lk(mParentSync);
1,990✔
85
#endif
86

87
    mParent = nullptr;
1,990✔
88
}
1,990✔
89

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

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

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

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

108
                mDispatcher = dispatcher;
2,000✔
109

110
                // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
111
                mEventsHandlerId = dispatcherPtr->registerEventHandler([ptrInstance]() {
41,131✔
112
                    bool handlerIsValid = false;
1,284,780✔
113
                    auto pThis = ptrInstance.lock();
1,284,780✔
114

115
                    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
116
                    if (pThis) {
1,284,780✔
117
                        pThis->dispatchEvents();
1,284,396✔
118
                        handlerIsValid = true;
119
                    }
120

121
                    // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
122
                    // cppcheck-suppress misra-c2012-15.5
123
                    return handlerIsValid;
1,284,780✔
124
                });
1,284,780✔
125

126
                // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
127
                mTimerHandlerId = dispatcherPtr->registerTimerHandler([ptrInstance](const TimerID_t timerId) {
16,390✔
128
                    bool handlerIsValid = false;
130✔
129
                    auto pThis = ptrInstance.lock();
130✔
130

131
                    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
132
                    if (pThis) {
130✔
133
                        pThis->dispatchTimerEvent(timerId);
130✔
134
                        handlerIsValid = true;
135
                    }
136

137
                    // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
138
                    // cppcheck-suppress misra-c2012-15.5
139
                    return handlerIsValid;
130✔
140
                });
130✔
141

142
                // cppcheck-suppress misra-c2012-13.1 ; false-positive. this is a functor, not initializer list
143
                mEnqueuedEventsHandlerId = dispatcherPtr->registerEnqueuedEventHandler([ptrInstance](const EventID_t event) {
16,030✔
144
                    bool handlerIsValid = false;
10✔
145
                    auto pThis = ptrInstance.lock();
10✔
146

147
                    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
148
                    if (pThis) {
10✔
149
                        pThis->transitionSimple(event);
10✔
150
                        handlerIsValid = true;
151
                    }
152

153
                    // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
154
                    // cppcheck-suppress misra-c2012-15.5
155
                    return handlerIsValid;
10✔
156
                });
10✔
157

158
                if ((INVALID_HSM_DISPATCHER_HANDLER_ID != mEventsHandlerId) &&
2,000✔
159
                    (INVALID_HSM_DISPATCHER_HANDLER_ID != mTimerHandlerId)) {
2,000✔
160
                    logHsmAction(HsmLogAction::IDLE,
2,000✔
161
                                 INVALID_HSM_STATE_ID,
162
                                 INVALID_HSM_STATE_ID,
163
                                 INVALID_HSM_EVENT_ID,
164
                                 false,
165
                                 VariantVector_t());
2,000✔
166
                    handleStartup();
2,000✔
167
                    result = true;
168
                } else {
169
                    HSM_TRACE_ERROR("failed to register event handlers");
×
170
                    dispatcherPtr->unregisterEventHandler(mEventsHandlerId);
×
171
                    dispatcherPtr->unregisterEnqueuedEventHandler(mEnqueuedEventsHandlerId);
×
172
                    dispatcherPtr->unregisterTimerHandler(mTimerHandlerId);
×
173
                }
174
            } else {
2,000✔
175
                HSM_TRACE_ERROR("failed to start dispatcher");
176
            }
177
        } else {
178
            HSM_TRACE_ERROR("dispatcher is NULL");
2,000✔
179
        }
180
    } else {
2,000✔
181
        HSM_TRACE_ERROR("already initialized");
2,000✔
182
    }
183

184
    return result;
2,000✔
185
}
186

187
bool HierarchicalStateMachine::Impl::isInitialized() const {
×
188
    return (false == mDispatcher.expired());
×
189
}
190

191
void HierarchicalStateMachine::Impl::release() {
3,980✔
192
    mStopDispatching = true;
3,980✔
193
    HSM_TRACE_CALL_DEBUG();
3,980✔
194

195
    disableHsmDebugging();
3,980✔
196

197
    auto dispatcherPtr = mDispatcher.lock();
3,980✔
198

199
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
200
    if (dispatcherPtr) {
3,980✔
201
        dispatcherPtr->unregisterEventHandler(mEventsHandlerId);
1,990✔
202
        dispatcherPtr->unregisterEnqueuedEventHandler(mEnqueuedEventsHandlerId);
1,990✔
203
        dispatcherPtr->unregisterTimerHandler(mTimerHandlerId);
1,990✔
204
        mDispatcher.reset();
1,990✔
205
        mEventsHandlerId = INVALID_HSM_DISPATCHER_HANDLER_ID;
1,990✔
206
    }
207
}
3,980✔
208

209
void HierarchicalStateMachine::Impl::registerFailedTransitionCallback(const HsmTransitionFailedCallback_t& onFailedTransition) {
210✔
210
    mFailedTransitionCallback = onFailedTransition;
210✔
211
}
210✔
212

213
void HierarchicalStateMachine::Impl::registerState(const StateID_t state,
5,420✔
214
                                                   HsmStateChangedCallback_t onStateChanged,
215
                                                   HsmStateEnterCallback_t onEntering,
216
                                                   HsmStateExitCallback_t onExiting) {
217
#ifdef HSM_ENABLE_SAFE_STRUCTURE
218
    if ((false == isSubstate(state)) && (false == isTopState(state))) {
5,420✔
219
        mTopLevelStates.push_back(state);
×
220
    }
221
#endif  // HSM_ENABLE_SAFE_STRUCTURE
222

223
    if (onStateChanged || onEntering || onExiting) {
5,420✔
224
        StateCallbacks cb;
4,000✔
225

226
        cb.onStateChanged = onStateChanged;
4,000✔
227
        cb.onEntering = onEntering;
4,000✔
228
        cb.onExiting = onExiting;
4,000✔
229
        mRegisteredStates[state] = cb;
4,000✔
230

231
        HSM_TRACE_CALL_DEBUG_ARGS("mRegisteredStates.size=%ld", mRegisteredStates.size());
4,000✔
232
    }
4,000✔
233
}
5,420✔
234

235
void HierarchicalStateMachine::Impl::registerFinalState(const StateID_t state,
190✔
236
                                                        const EventID_t event,
237
                                                        HsmStateChangedCallback_t onStateChanged,
238
                                                        HsmStateEnterCallback_t onEntering,
239
                                                        HsmStateExitCallback_t onExiting) {
240
    mFinalStates.emplace(state, event);
190✔
241
    registerState(state, onStateChanged, onEntering, onExiting);
380✔
242
}
190✔
243

244
void HierarchicalStateMachine::Impl::registerHistory(const StateID_t parent,
100✔
245
                                                     const StateID_t historyState,
246
                                                     const HistoryType type,
247
                                                     const StateID_t defaultTarget,
248
                                                     HsmTransitionCallback_t transitionCallback) {
249
    (void)mHistoryStates.emplace(parent, historyState);
100✔
250
    mHistoryData.emplace(historyState, HistoryInfo(type, defaultTarget, transitionCallback));
100✔
251
}
100✔
252

253
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent, const StateID_t substate) {
670✔
254
    return registerSubstate(parent, substate, false);
670✔
255
}
256

257
bool HierarchicalStateMachine::Impl::registerSubstateEntryPoint(const StateID_t parent,
860✔
258
                                                                const StateID_t substate,
259
                                                                const EventID_t onEvent,
260
                                                                const HsmTransitionConditionCallback_t& conditionCallback,
261
                                                                const bool expectedConditionValue) {
262
    return registerSubstate(parent, substate, true, onEvent, conditionCallback, expectedConditionValue);
860✔
263
}
264

265
void HierarchicalStateMachine::Impl::registerTimer(const TimerID_t timerID, const EventID_t event) {
140✔
266
    mTimers.emplace(timerID, event);
140✔
267
}
140✔
268

269
bool HierarchicalStateMachine::Impl::registerSubstate(const StateID_t parent,
1,530✔
270
                                                      const StateID_t substate,
271
                                                      const bool isEntryPoint,
272
                                                      const EventID_t onEvent,
273
                                                      const HsmTransitionConditionCallback_t& conditionCallback,
274
                                                      const bool expectedConditionValue) {
275
    bool registrationAllowed = false;
1,530✔
276

277
#ifdef HSM_ENABLE_SAFE_STRUCTURE
278
    // do a simple sanity check
279
    if (parent != substate) {
1,530✔
280
        StateID_t curState = parent;
1,530✔
281
        StateID_t prevState;
1,530✔
282

283
        if (false == hasParentState(substate, prevState)) {
1,530✔
284
            registrationAllowed = true;
1,960✔
285

286
            while (true == hasParentState(curState, prevState)) {
1,960✔
287
                if (substate == prevState) {
470✔
288
                    HSM_TRACE_CALL_DEBUG_ARGS(
289
                        "requested operation will result in substates recursion (parent=<%s>, substate=<%s>)",
290
                        getStateName(parent).c_str(),
291
                        getStateName(substate).c_str());
292
                    registrationAllowed = false;
293
                    break;
294
                }
295

296
                curState = prevState;
297
            }
298
        } else {
299
            HSM_TRACE_CALL_DEBUG_ARGS("substate <%s> already has a parent <%s>",
300
                                      getStateName(substate).c_str(),
301
                                      getStateName(prevState).c_str());
1,530✔
302
        }
303
    }
304
#else
305
    registrationAllowed = (parent != substate);
306
#endif  // HSM_ENABLE_SAFE_STRUCTURE
307

308
    if (registrationAllowed) {
1,530✔
309
        // NOTE: false-positive. isEntryPoint is of type bool
310
        // cppcheck-suppress misra-c2012-14.4
311
        if (isEntryPoint) {
1,490✔
312
            StateEntryPoint entryInfo;
820✔
313

314
            entryInfo.state = substate;
820✔
315
            entryInfo.onEvent = onEvent;
820✔
316
            entryInfo.checkCondition = conditionCallback;
820✔
317
            entryInfo.expectedConditionValue = expectedConditionValue;
820✔
318

319
            (void)mSubstateEntryPoints.emplace(parent, entryInfo);
820✔
320
        }
820✔
321

322
        (void)mSubstates.emplace(parent, substate);
1,490✔
323

324
#ifdef HSM_ENABLE_SAFE_STRUCTURE
325
        if (true == isTopState(substate)) {
1,490✔
326
            mTopLevelStates.remove(substate);
1,490✔
327
        }
328
#endif  // HSM_ENABLE_SAFE_STRUCTURE
329
    }
330

331
    return registrationAllowed;
1,530✔
332
}
333

334
bool HierarchicalStateMachine::Impl::registerStateAction(const StateID_t state,
170✔
335
                                                         const StateActionTrigger actionTrigger,
336
                                                         const StateAction action,
337
                                                         const VariantVector_t& args) {
338
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>, actionTrigger=%d, action=%d",
339
                              getStateName(state).c_str(),
340
                              SC2INT(actionTrigger),
341
                              SC2INT(action));
170✔
342
    bool result = false;
170✔
343
    bool argsValid = false;
170✔
344
    StateActionInfo newAction;
170✔
345

346
    newAction.actionArgs = args;
170✔
347

348
    // validate arguments
349
    switch (action) {
170✔
350
        case StateAction::START_TIMER:
100✔
351
            argsValid = (newAction.actionArgs.size() == 3u) && newAction.actionArgs[0].isNumeric() &&
200✔
352
                        newAction.actionArgs[1].isNumeric() && newAction.actionArgs[2].isBool();
300✔
353
            break;
354
        case StateAction::RESTART_TIMER:
20✔
355
        case StateAction::STOP_TIMER:
20✔
356
            argsValid = (newAction.actionArgs.size() == 1u) && newAction.actionArgs[0].isNumeric();
20✔
357
            break;
358
        case StateAction::TRANSITION:
50✔
359
            argsValid = (newAction.actionArgs.size() >= 1u) && newAction.actionArgs[0].isNumeric();
50✔
360
            break;
361
        default:
362
            // do nothing
363
            break;
364
    }
365

366
    if (true == argsValid) {
367
        newAction.action = action;
170✔
368
        (void)mRegisteredActions.emplace(std::make_pair(state, actionTrigger), newAction);
170✔
369
        result = true;
170✔
370
    } else {
371
        HSM_TRACE_ERROR("invalid arguments");
170✔
372
    }
373

374
    return result;
170✔
375
}
170✔
376

377
void HierarchicalStateMachine::Impl::registerTransition(const StateID_t from,
4,550✔
378
                                                        const StateID_t to,
379
                                                        const EventID_t onEvent,
380
                                                        HsmTransitionCallback_t transitionCallback,
381
                                                        HsmTransitionConditionCallback_t conditionCallback,
382
                                                        const bool expectedConditionValue) {
383
    (void)mTransitionsByEvent.emplace(std::make_pair(from, onEvent),
4,550✔
384
                                      TransitionInfo(from,
4,550✔
385
                                                     to,
386
                                                     TransitionType::EXTERNAL_TRANSITION,
387
                                                     transitionCallback,
388
                                                     conditionCallback,
389
                                                     expectedConditionValue));
390
}
4,550✔
391

392
void HierarchicalStateMachine::Impl::registerSelfTransition(const StateID_t state,
70✔
393
                                                            const EventID_t onEvent,
394
                                                            const TransitionType type,
395
                                                            HsmTransitionCallback_t transitionCallback,
396
                                                            HsmTransitionConditionCallback_t conditionCallback,
397
                                                            const bool expectedConditionValue) {
398
    (void)mTransitionsByEvent.emplace(
140✔
399
        std::make_pair(state, onEvent),
70✔
400
        TransitionInfo(state, state, type, transitionCallback, conditionCallback, expectedConditionValue));
70✔
401
}
70✔
402

403
StateID_t HierarchicalStateMachine::Impl::getLastActiveState() const {
750✔
404
    StateID_t currentState = INVALID_HSM_STATE_ID;
750✔
405

406
    if (false == mActiveStates.empty()) {
750✔
407
        currentState = mActiveStates.back();
750✔
408
    }
409

410
    return currentState;
750✔
411
}
412

413
const std::list<StateID_t>& HierarchicalStateMachine::Impl::getActiveStates() const {
1,260✔
414
    return mActiveStates;
1,260✔
415
}
416

417
bool HierarchicalStateMachine::Impl::isStateActive(const StateID_t state) const {
2,054,814✔
418
    return (std::find(mActiveStates.begin(), mActiveStates.end(), state) != mActiveStates.end());
2,054,814✔
419
}
420

421
void HierarchicalStateMachine::Impl::transitionWithArgsArray(const EventID_t event, const VariantVector_t& args) {
50✔
422
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, args.size=%lu", getEventName(event).c_str(), args.size());
50✔
423

424
    (void)transitionExWithArgsArray(event, false, false, 0, args);
50✔
425
}
50✔
426

427
bool HierarchicalStateMachine::Impl::transitionExWithArgsArray(const EventID_t event,
1,002,630✔
428
                                                               const bool clearQueue,
429
                                                               const bool sync,
430
                                                               const int timeoutMs,
431
                                                               const VariantVector_t& args) {
432
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, clearQueue=%s, sync=%s, args.size=%lu",
433
                              getEventName(event).c_str(),
434
                              BOOL2STR(clearQueue),
435
                              BOOL2STR(sync),
436
                              args.size());
1,002,630✔
437

438
    bool status = false;
1,002,630✔
439
    auto dispatcherPtr = mDispatcher.lock();
1,002,630✔
440

441
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
442
    if (dispatcherPtr) {
1,002,630✔
443
        PendingEventInfo eventInfo;
1,002,620✔
444

445
        eventInfo.type = event;
1,002,620✔
446
        eventInfo.args = args;
1,002,620✔
447

448
        if (true == sync) {
1,002,620✔
449
            eventInfo.initLock();
1,410✔
450
        }
451

452
        {
1,002,620✔
453
            HSM_SYNC_EVENTS_QUEUE();
1,002,620✔
454

455
            if (true == clearQueue) {
1,002,620✔
456
                clearPendingEvents();
10✔
457
            }
458

459
            mPendingEvents.push_back(eventInfo);
1,002,620✔
460
        }
1,002,620✔
461

462
        HSM_TRACE_DEBUG("transitionEx: emit");
1,002,620✔
463
        dispatcherPtr->emitEvent(mEventsHandlerId);
1,002,620✔
464

465
        if (true == sync) {
1,002,620✔
466
            HSM_TRACE_DEBUG("transitionEx: wait...");
1,410✔
467
            eventInfo.wait(timeoutMs);
1,410✔
468
            status = (HsmEventStatus_t::DONE_OK == *eventInfo.transitionStatus);
1,410✔
469
        } else {
470
            // always return true for async transitions
471
            status = true;
472
        }
473
    } else {
1,002,620✔
474
        HSM_TRACE_ERROR("HSM is not initialized");
1,002,630✔
475
    }
476

477
    return status;
1,002,630✔
478
}
1,002,630✔
479

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

485
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
486
    if (dispatcherPtr) {
10✔
487
        res = dispatcherPtr->enqueueEvent(mEnqueuedEventsHandlerId, event);
10✔
488
    }
489

490
    return res;
10✔
491
}
10✔
492

493
bool HierarchicalStateMachine::Impl::isTransitionPossible(const EventID_t event, const VariantVector_t& args) {
60✔
494
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
495
    bool possible = false;
60✔
496

497
    for (auto it = mActiveStates.begin(); it != mActiveStates.end(); ++it) {
100✔
498
        possible = checkTransitionPossibility(*it, event, args);
60✔
499

500
        if (true == possible) {
60✔
501
            break;
502
        }
503
    }
504

505
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
506
    return possible;
60✔
507
}
508

509
void HierarchicalStateMachine::Impl::startTimer(const TimerID_t timerID,
40✔
510
                                                const unsigned int intervalMs,
511
                                                const bool isSingleShot) {
512
    auto dispatcherPtr = mDispatcher.lock();
40✔
513

514
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
515
    if (dispatcherPtr) {
40✔
516
        dispatcherPtr->startTimer(mTimerHandlerId, timerID, intervalMs, isSingleShot);
40✔
517
    }
518
}
40✔
519

520
void HierarchicalStateMachine::Impl::restartTimer(const TimerID_t timerID) {
10✔
521
    auto dispatcherPtr = mDispatcher.lock();
10✔
522

523
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
524
    if (dispatcherPtr) {
10✔
525
        dispatcherPtr->restartTimer(timerID);
10✔
526
    }
527
}
10✔
528

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

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

538
bool HierarchicalStateMachine::Impl::isTimerRunning(const TimerID_t timerID) {
30✔
539
    bool running = false;
30✔
540
    auto dispatcherPtr = mDispatcher.lock();
30✔
541

542
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
543
    if (dispatcherPtr) {
30✔
544
        running = dispatcherPtr->isTimerRunning(timerID);
30✔
545
    }
546

547
    return running;
30✔
548
}
30✔
549

550
// ============================================================================
551
// PRIVATE
552
// ============================================================================
553
void HierarchicalStateMachine::Impl::handleStartup() {
2,000✔
554
    HSM_TRACE_CALL_DEBUG_ARGS("mActiveStates.size=%ld", mActiveStates.size());
2,000✔
555
    auto dispatcherPtr = mDispatcher.lock();
2,000✔
556

557
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
558
    if (dispatcherPtr) {
2,000✔
559
        HSM_TRACE_DEBUG("state=<%s>", getStateName(mInitialState).c_str());
2,000✔
560
        std::list<StateID_t> entryPoints;
2,000✔
561

562
        (void)onStateEntering(mInitialState, VariantVector_t());
2,000✔
563
        mActiveStates.push_back(mInitialState);
2,000✔
564
        onStateChanged(mInitialState, VariantVector_t());
2,000✔
565

566
        if (true == getEntryPoints(mInitialState, INVALID_HSM_EVENT_ID, VariantVector_t(), entryPoints)) {
2,000✔
567
            PendingEventInfo entryPointTransitionEvent;
130✔
568

569
            entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
130✔
570
            entryPointTransitionEvent.type = INVALID_HSM_EVENT_ID;
130✔
571

572
            {
130✔
573
                HSM_SYNC_EVENTS_QUEUE();
130✔
574
                mPendingEvents.push_front(entryPointTransitionEvent);
130✔
575
            }
130✔
576
        }
130✔
577

578
        if (false == mPendingEvents.empty()) {
2,000✔
579
            dispatcherPtr->emitEvent(mEventsHandlerId);
130✔
580
        }
581
    }
2,000✔
582
}
2,000✔
583

584
void HierarchicalStateMachine::Impl::transitionSimple(const EventID_t event) {
140✔
585
    (void)transitionExWithArgsArray(event, false, false, 0, VariantVector_t());
140✔
586
}
140✔
587

588
void HierarchicalStateMachine::Impl::dispatchEvents() {
1,284,396✔
589
    HSM_TRACE_CALL_DEBUG_ARGS("mPendingEvents.size=%ld", mPendingEvents.size());
1,284,396✔
590
    auto dispatcherPtr = mDispatcher.lock();
1,284,396✔
591

592
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
593
    if (dispatcherPtr) {
1,284,396✔
594
        if (false == mStopDispatching) {
1,283,724✔
595
            if (false == mPendingEvents.empty()) {
1,283,252✔
596
                PendingEventInfo pendingEvent;
683,768✔
597

598
                {
683,768✔
599
                    HSM_SYNC_EVENTS_QUEUE();
683,768✔
600
                    pendingEvent = mPendingEvents.front();
683,768✔
601
                    mPendingEvents.pop_front();
683,768✔
602
                }
683,768✔
603

604
                HsmEventStatus_t transitiontStatus = doTransition(pendingEvent);
683,768✔
605

606
                HSM_TRACE_DEBUG("unlock with status %d", SC2INT(transitiontStatus));
683,768✔
607
                pendingEvent.unlock(transitiontStatus);
683,768✔
608
            }
683,768✔
609

610
            if ((false == mStopDispatching) && (false == mPendingEvents.empty())) {
1,283,252✔
611
                dispatcherPtr->emitEvent(mEventsHandlerId);
680,586✔
612
            }
613
        }
614
    }
615
}
1,284,396✔
616

617
void HierarchicalStateMachine::Impl::dispatchTimerEvent(const TimerID_t id) {
130✔
618
    HSM_TRACE_CALL_DEBUG_ARGS("id=%d", SC2INT(id));
130✔
619
    auto it = mTimers.find(id);
130✔
620

621
    if (mTimers.end() != it) {
130✔
622
        transitionSimple(it->second);
130✔
623
    }
624
}
130✔
625

626
bool HierarchicalStateMachine::Impl::onStateExiting(const StateID_t state) {
683,238✔
627
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
683,238✔
628
    bool res = true;
683,238✔
629
    auto it = mRegisteredStates.find(state);
683,238✔
630

631
    if ((mRegisteredStates.end() != it) && it->second.onExiting) {
683,238✔
632
        res = it->second.onExiting();
330✔
633
        logHsmAction(HsmLogAction::CALLBACK_EXIT,
330✔
634
                     state,
635
                     INVALID_HSM_STATE_ID,
636
                     INVALID_HSM_EVENT_ID,
637
                     (false == res),
638
                     VariantVector_t());
330✔
639
    }
640

641
    // execute state action only if transition was accepted by client
642
    if (true == res) {
330✔
643
        executeStateAction(state, StateActionTrigger::ON_STATE_EXIT);
683,218✔
644
    }
645

646
    return res;
683,238✔
647
}
648

649
bool HierarchicalStateMachine::Impl::onStateEntering(const StateID_t state, const VariantVector_t& args) {
685,898✔
650
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
685,898✔
651
    bool res = true;
685,898✔
652

653
    // since we can have a situation when same state is entered twice (parallel transitions) there
654
    // is no need to call callbacks multiple times
655
    if (false == isStateActive(state)) {
685,898✔
656
        auto it = mRegisteredStates.find(state);
685,868✔
657

658
        if ((mRegisteredStates.end() != it) && it->second.onEntering) {
685,868✔
659
            res = it->second.onEntering(args);
540✔
660
            logHsmAction(HsmLogAction::CALLBACK_ENTER, INVALID_HSM_STATE_ID, state, INVALID_HSM_EVENT_ID, (false == res), args);
540✔
661
        }
662

663
        // execute state action only if transition was accepted by client
664
        if (true == res) {
540✔
665
            executeStateAction(state, StateActionTrigger::ON_STATE_ENTRY);
685,848✔
666
        }
667
    }
668

669
    return res;
685,898✔
670
}
671

672
void HierarchicalStateMachine::Impl::onStateChanged(const StateID_t state, const VariantVector_t& args) {
685,848✔
673
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>", getStateName(state).c_str());
685,848✔
674
    auto it = mRegisteredStates.find(state);
685,848✔
675

676
    if ((mRegisteredStates.end() != it) && it->second.onStateChanged) {
685,848✔
677
        it->second.onStateChanged(args);
683,768✔
678
        logHsmAction(HsmLogAction::CALLBACK_STATE, INVALID_HSM_STATE_ID, state, INVALID_HSM_EVENT_ID, false, args);
683,768✔
679
    } else {
680
        HSM_TRACE_WARNING("no callback registered for state <%s>", getStateName(state).c_str());
685,848✔
681
    }
682
}
685,848✔
683

684
void HierarchicalStateMachine::Impl::executeStateAction(const StateID_t state, const StateActionTrigger actionTrigger) {
1,369,066✔
685
    HSM_TRACE_CALL_DEBUG_ARGS("state=<%s>, actionTrigger=%d", getStateName(state).c_str(), SC2INT(actionTrigger));
1,369,066✔
686
    auto dispatcherPtr = mDispatcher.lock();
1,369,066✔
687

688
    // cppcheck-suppress misra-c2012-14.4 ; false-positive. std::shated_ptr has a bool() operator
689
    if (dispatcherPtr) {
1,369,066✔
690
        auto key = std::make_pair(state, actionTrigger);
1,368,858✔
691
        auto itRange = mRegisteredActions.equal_range(key);
1,368,858✔
692

693
        if (itRange.first != itRange.second) {
1,368,858✔
694
            switch (actionTrigger) {
160✔
695
                case StateActionTrigger::ON_STATE_ENTRY:
120✔
696
                    logHsmAction(HsmLogAction::ON_ENTER_ACTIONS, INVALID_HSM_STATE_ID, state);
120✔
697
                    break;
120✔
698
                case StateActionTrigger::ON_STATE_EXIT:
40✔
699
                    logHsmAction(HsmLogAction::ON_EXIT_ACTIONS, INVALID_HSM_STATE_ID, state);
40✔
700
                    break;
40✔
701
                default:
702
                    // NOTE: do nothing
703
                    break;
704
            }
705

706
            for (auto it = itRange.first; it != itRange.second; ++it) {
340✔
707
                const StateActionInfo& actionInfo = it->second;
180✔
708

709
                if (StateAction::START_TIMER == actionInfo.action) {
180✔
710
                    dispatcherPtr->startTimer(mTimerHandlerId,
220✔
711
                                              actionInfo.actionArgs[0].toInt64(),
110✔
712
                                              actionInfo.actionArgs[1].toInt64(),
110✔
713
                                              actionInfo.actionArgs[2].toBool());
110✔
714
                } else if (StateAction::STOP_TIMER == actionInfo.action) {
70✔
715
                    dispatcherPtr->stopTimer(actionInfo.actionArgs[0].toInt64());
10✔
716
                } else if (StateAction::RESTART_TIMER == actionInfo.action) {
60✔
717
                    dispatcherPtr->restartTimer(actionInfo.actionArgs[0].toInt64());
10✔
718
                } else if (StateAction::TRANSITION == actionInfo.action) {
50✔
719
                    VariantVector_t transitionArgs;
50✔
720

721
                    if (actionInfo.actionArgs.size() > 1u) {
50✔
722
                        transitionArgs.reserve(actionInfo.actionArgs.size() - 1u);
10✔
723

724
                        for (size_t i = 1; i < actionInfo.actionArgs.size(); ++i) {
30✔
725
                            transitionArgs.push_back(actionInfo.actionArgs[i]);
20✔
726
                        }
727
                    }
728

729
                    transitionWithArgsArray(static_cast<EventID_t>(actionInfo.actionArgs[0].toInt64()), transitionArgs);
50✔
730
                } else {
50✔
731
                    HSM_TRACE_WARNING("unsupported action <%d>", SC2INT(actionInfo.action));
180✔
732
                }
733
            }
734
        }
735
    }
736
}
1,369,066✔
737

738
bool HierarchicalStateMachine::Impl::getParentState(const StateID_t child, StateID_t& outParent) {
1,371,646✔
739
    bool wasFound = false;
1,371,646✔
740
    auto it = std::find_if(mSubstates.begin(), mSubstates.end(), [child](const std::pair<StateID_t, StateID_t>& item) {
1,371,646✔
741
        // cppcheck-suppress misra-c2012-15.5 ; false-positive. "return" statement belongs to lambda function
742
        return (child == item.second);
19,390✔
743
    });
744

745
    if (mSubstates.end() != it) {
1,371,646✔
746
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
4,750✔
747
        wasFound = true;
4,750✔
748
    }
749

750
    return wasFound;
1,371,646✔
751
}
752
bool HierarchicalStateMachine::Impl::isSubstateOf(const StateID_t parent, const StateID_t child) {
685,798✔
753
    HSM_TRACE_CALL_DEBUG_ARGS("parent=<%s>, child=<%s>", getStateName(parent).c_str(), getStateName(child).c_str());
685,798✔
754
    StateID_t curState = child;
685,798✔
755

756
    do {
687,038✔
757
        if (false == getParentState(curState, curState)) {
687,038✔
758
            break;
759
        }
760
    } while (parent != curState);
3,340✔
761

762
    return (parent == curState);
685,798✔
763
}
764

765
bool HierarchicalStateMachine::Impl::getHistoryParent(const StateID_t historyState, StateID_t& outParent) {
100✔
766
    bool wasFound = false;
100✔
767
    auto it =
100✔
768
        std::find_if(mHistoryStates.begin(), mHistoryStates.end(), [historyState](const std::pair<StateID_t, StateID_t>& item) {
100✔
769
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
770
            // cppcheck-suppress misra-c2012-15.5
771
            return (historyState == item.second);
100✔
772
        });
773

774
    if (mHistoryStates.end() != it) {
100✔
775
        outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
100✔
776
        wasFound = true;
100✔
777
    }
778

779
    return wasFound;
100✔
780
}
781

782
void HierarchicalStateMachine::Impl::updateHistory(const StateID_t topLevelState, const std::list<StateID_t>& exitedStates) {
682,908✔
783
    HSM_TRACE_CALL_DEBUG_ARGS("topLevelState=<%s>, exitedStates.size=%ld",
784
                              getStateName(topLevelState).c_str(),
785
                              exitedStates.size());
682,908✔
786

787
    std::list<std::list<StateID_t>*> upatedHistory;
682,908✔
788

789
    for (auto itActiveState = exitedStates.begin(); itActiveState != exitedStates.end(); ++itActiveState) {
1,366,286✔
790
        StateID_t curState = *itActiveState;
683,378✔
791
        StateID_t parentState;
683,378✔
792

793
        while (true == getParentState(curState, parentState)) {
684,098✔
794
            HSM_TRACE_DEBUG("curState=<%s>, parentState=<%s>",
795
                            getStateName(curState).c_str(),
796
                            getStateName(parentState).c_str());
1,190✔
797
            auto itRange = mHistoryStates.equal_range(parentState);
1,190✔
798

799
            if (itRange.first != itRange.second) {
1,190✔
800
                HSM_TRACE_DEBUG("parent=<%s> has history items", getStateName(parentState).c_str());
801

802
                for (auto it = itRange.first; it != itRange.second; ++it) {
680✔
803
                    auto itCurHistory = mHistoryData.find(it->second);
340✔
804

805
                    if (itCurHistory != mHistoryData.end()) {
340✔
806
                        auto itUpdatedHistory =
340✔
807
                            std::find(upatedHistory.begin(), upatedHistory.end(), &itCurHistory->second.previousActiveStates);
340✔
808

809
                        if (itUpdatedHistory == upatedHistory.end()) {
340✔
810
                            itCurHistory->second.previousActiveStates.clear();
200✔
811
                            upatedHistory.push_back(&(itCurHistory->second.previousActiveStates));
200✔
812
                        } else {
813
                        }
814

815
                        if (HistoryType::SHALLOW == itCurHistory->second.type) {
340✔
816
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
160✔
817
                                          itCurHistory->second.previousActiveStates.end(),
160✔
818
                                          curState) == itCurHistory->second.previousActiveStates.end()) {
320✔
819
                                HSM_TRACE_DEBUG("SHALLOW -> store state <%s> in history of parent <%s>",
820
                                                getStateName(curState).c_str(),
821
                                                getStateName(it->second).c_str());
120✔
822
                                itCurHistory->second.previousActiveStates.push_back(curState);
120✔
823
                            }
824
                        } else if (HistoryType::DEEP == itCurHistory->second.type) {
180✔
825
                            if (std::find(itCurHistory->second.previousActiveStates.begin(),
180✔
826
                                          itCurHistory->second.previousActiveStates.end(),
180✔
827
                                          *itActiveState) == itCurHistory->second.previousActiveStates.end()) {
360✔
828
                                HSM_TRACE_DEBUG("DEEP -> store state <%s> in history of parent <%s>",
829
                                                getStateName(*itActiveState).c_str(),
830
                                                getStateName(it->second).c_str());
180✔
831
                                itCurHistory->second.previousActiveStates.push_back(*itActiveState);
340✔
832
                            }
833
                        } else {
834
                            // NOTE: do nothing
835
                        }
836
                    }
837
                }
838
            }
839

840
            if (topLevelState != parentState) {
1,190✔
841
                curState = parentState;
720✔
842
            } else {
843
                break;
844
            }
845
        }
846
    }
847
}
682,908✔
848

849
bool HierarchicalStateMachine::Impl::checkTransitionPossibility(const StateID_t fromState,
60✔
850
                                                                const EventID_t event,
851
                                                                const VariantVector_t& args) {
852
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>", getEventName(event).c_str());
60✔
853

854
    StateID_t currentState = fromState;
60✔
855
    std::list<TransitionInfo> possibleTransitions;
60✔
856
    EventID_t nextEvent;
60✔
857
    bool possible = true;
60✔
858

859
    {
60✔
860
        // NOTE: findTransitionTarget can be a bit heavy. possible optimization to reduce lock time is
861
        //       to make a copy of mPendingEvents and work with it
862
        HSM_SYNC_EVENTS_QUEUE();
60✔
863

864
        for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()) && (true == possible); ++it) {
60✔
865
            nextEvent = it->type;
×
866
            possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
×
867

868
            if (true == possible) {
×
869
                if (false == possibleTransitions.empty()) {
×
870
                    currentState = possibleTransitions.front().destinationState;
×
871
                } else {
872
                    possible = false;
873
                    break;
874
                }
875
            }
876
        }
877
    }
60✔
878

879
    if (true == possible) {
60✔
880
        nextEvent = event;
60✔
881
        possible = findTransitionTarget(currentState, nextEvent, args, true, possibleTransitions);
60✔
882
    }
883

884
    HSM_TRACE_CALL_RESULT("%d", BOOL2INT(possible));
60✔
885
    return possible;
60✔
886
}
60✔
887

888
bool HierarchicalStateMachine::Impl::findTransitionTarget(const StateID_t fromState,
683,498✔
889
                                                          const EventID_t event,
890
                                                          const VariantVector_t& transitionArgs,
891
                                                          const bool searchParents,
892
                                                          std::list<TransitionInfo>& outTransitions) {
893
    HSM_TRACE_CALL_DEBUG_ARGS("fromState=<%s>, event=<%s>", getStateName(fromState).c_str(), getEventName(event).c_str());
683,498✔
894
    bool continueSearch;
683,498✔
895
    StateID_t curState = fromState;
683,498✔
896

897
    do {
683,498✔
898
        auto key = std::make_pair(curState, event);
683,498✔
899
        auto itRange = mTransitionsByEvent.equal_range(key);
683,498✔
900

901
        continueSearch = false;
683,498✔
902

903
        if (itRange.first == itRange.second) {
683,498✔
904
            if (true == searchParents) {
620✔
905
                StateID_t parentState;
40✔
906
                bool hasParent = getParentState(curState, parentState);
40✔
907

908
                if (true == hasParent) {
40✔
909
                    curState = parentState;
×
910
                    continueSearch = true;
×
911
                }
912
            }
913
        } else {
914
            for (auto it = itRange.first; it != itRange.second; ++it) {
1,366,016✔
915
                HSM_TRACE_DEBUG("check transition to <%s>...", getStateName(it->second.destinationState).c_str());
683,138✔
916

917
                if ((nullptr == it->second.checkCondition) ||
683,138✔
918
                    (it->second.expectedConditionValue == it->second.checkCondition(transitionArgs))) {
140✔
919
                    bool wasFound = false;
683,048✔
920
                    std::list<StateID_t> parentStates = {it->second.destinationState};
683,048✔
921

922
                    // cppcheck-suppress misra-c2012-15.4
923
                    do {
683,518✔
924
                        StateID_t currentParent = parentStates.front();
683,518✔
925

926
                        parentStates.pop_front();
683,518✔
927

928
                        // if state has substates we must check if transition into them is possible (after cond)
929
                        if (true == hasSubstates(currentParent)) {
683,518✔
930
                            if (true == hasEntryPoint(currentParent)) {
520✔
931
                                HSM_TRACE_DEBUG("state <%s> has entrypoints", getStateName(currentParent).c_str());
490✔
932
                                std::list<StateID_t> entryPoints;
490✔
933

934
                                if (true == getEntryPoints(currentParent, event, transitionArgs, entryPoints)) {
490✔
935
                                    parentStates.splice(parentStates.end(), entryPoints);
940✔
936
                                } else {
937
                                    HSM_TRACE_WARNING("no matching entrypoints found");
938
                                    break;
939
                                }
940
                            } else {
20✔
941
                                HSM_TRACE_WARNING("state <%s> doesn't have an entrypoint defined",
942
                                                  getStateName(currentParent).c_str());
943
                                break;
944
                            }
945
                        } else {
946
                            outTransitions.push_back(it->second);
683,048✔
947
                            wasFound = true;
948
                        }
949
                    } while ((false == wasFound) && (parentStates.empty() == false));
470✔
950
                }
683,048✔
951
            }
952
        }
953
    } while (true == continueSearch);
683,498✔
954

955
    HSM_TRACE_CALL_RESULT("%s", BOOL2STR(outTransitions.empty() == false));
683,498✔
956
    return (outTransitions.empty() == false);
683,498✔
957
}
958

959
typename HsmEventStatus_t HierarchicalStateMachine::Impl::doTransition(const PendingEventInfo& event) {
683,768✔
960
    HSM_TRACE_CALL_DEBUG_ARGS("event=<%s>, transitionType=%d", getEventName(event.type).c_str(), SC2INT(event.transitionType));
683,768✔
961
    HsmEventStatus_t res = HsmEventStatus_t::DONE_FAILED;
683,768✔
962
    auto activeStatesSnapshot = mActiveStates;
683,768✔
963
    std::list<StateID_t> acceptedStates;  // list of states that accepted transitions
683,768✔
964

965
    for (auto it = activeStatesSnapshot.rbegin(); it != activeStatesSnapshot.rend(); ++it) {
1,368,816✔
966
        // in case of parallel transitions some states might become inactive after handleSingleTransition()
967
        // example: [*B, *C] -> D
968
        if (true == isStateActive(*it)) {
685,048✔
969
            // we don't need to process transitions for active states if their child already processed it
970
            bool childStateProcessed = false;
685,048✔
971

972
            for (const auto& state : acceptedStates) {
685,118✔
973
                if (true == isSubstateOf(*it, state)) {
770✔
974
                    childStateProcessed = true;
975
                    break;
976
                }
977
            }
978

979
            if (false == childStateProcessed) {
685,048✔
980
                const HsmEventStatus_t singleTransitionResult = handleSingleTransition(*it, event);
684,348✔
981

982
                switch (singleTransitionResult) {
684,348✔
983
                    case HsmEventStatus_t::PENDING:
750✔
984
                        res = singleTransitionResult;
750✔
985
                        acceptedStates.push_back(*it);
750✔
986
                        break;
987
                    case HsmEventStatus_t::DONE_OK:
682,888✔
988
                        logHsmAction(HsmLogAction::IDLE,
682,888✔
989
                                     INVALID_HSM_STATE_ID,
990
                                     INVALID_HSM_STATE_ID,
991
                                     INVALID_HSM_EVENT_ID,
992
                                     false,
993
                                     VariantVector_t());
682,888✔
994
                        if (HsmEventStatus_t::PENDING != res) {
682,888✔
995
                            res = singleTransitionResult;
682,888✔
996
                        }
997
                        acceptedStates.push_back(*it);
685,048✔
998
                        break;
999
                    case HsmEventStatus_t::CANCELED:
1000
                    case HsmEventStatus_t::DONE_FAILED:
1001
                    default:
1002
                        // do nothing
1003
                        break;
1004
                }
1005
            }
1006
        }
1007
    }
1008

1009
    if (mFailedTransitionCallback && ((HsmEventStatus_t::DONE_FAILED == res) || (HsmEventStatus_t::CANCELED == res))) {
683,768✔
1010
        mFailedTransitionCallback(event.type, event.args);
120✔
1011
    }
1012

1013
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
683,768✔
1014
    return res;
683,768✔
1015
}
1,367,536✔
1016

1017
typename HsmEventStatus_t HierarchicalStateMachine::Impl::handleSingleTransition(const StateID_t activeState,
684,348✔
1018
                                                                                 const PendingEventInfo& event) {
1019
    HSM_TRACE_CALL_DEBUG_ARGS("activeState=<%s>, event=<%s>, transitionType=%d",
1020
                              getStateName(activeState).c_str(),
1021
                              getEventName(event.type).c_str(),
1022
                              SC2INT(event.transitionType));
684,348✔
1023
    HsmEventStatus_t res = HsmEventStatus_t::DONE_FAILED;
684,348✔
1024
    const StateID_t fromState = activeState;
684,348✔
1025
    bool isCorrectTransition = false;
684,348✔
1026
    std::list<TransitionInfo> matchingTransitions;
684,348✔
1027

1028
    DEBUG_DUMP_ACTIVE_STATES();
684,348✔
1029

1030
    // ========================================================
1031
    // determine target state based on current transition
1032
    if (TransitionBehavior::REGULAR == event.transitionType) {
684,348✔
1033
        isCorrectTransition = findTransitionTarget(fromState, event.type, event.args, false, matchingTransitions);
683,438✔
1034

1035
        if (false == isCorrectTransition) {
1036
            HSM_TRACE_WARNING("no suitable transition from state <%s> with event <%s>",
1037
                              getStateName(fromState).c_str(),
1038
                              getEventName(event.type).c_str());
1039
        }
1040
    } else if (TransitionBehavior::ENTRYPOINT == event.transitionType) {
910✔
1041
        isCorrectTransition = true;
660✔
1042

1043
        // if fromState doesnt have active children
1044
        for (auto it = mActiveStates.rbegin(); it != mActiveStates.rend(); ++it) {
1,640✔
1045
            if (fromState != *it) {
980✔
1046
                StateID_t activeParent = INVALID_HSM_STATE_ID;
320✔
1047

1048
                if (true == getParentState(*it, activeParent)) {
320✔
1049
                    if (activeParent == fromState) {
80✔
1050
                        // no need to handle entry transition for already active state
1051
                        isCorrectTransition = false;
×
1052
                        break;
×
1053
                    }
1054
                }
1055
            }
1056
        }
1057

1058
        if (true == isCorrectTransition) {
×
1059
            std::list<StateID_t> entryStates;
660✔
1060

1061
            isCorrectTransition = getEntryPoints(fromState, event.type, event.args, entryStates);
660✔
1062

1063
            if (true == isCorrectTransition) {
660✔
1064
                for (auto it = entryStates.begin(); it != entryStates.end(); ++it) {
1,300✔
1065
                    (void)matchingTransitions.emplace_back(
1,340✔
1066
                        TransitionInfo{fromState, *it, TransitionType::EXTERNAL_TRANSITION, nullptr, nullptr});
1,340✔
1067
                }
1068
            } else {
1069
                HSM_TRACE_WARNING("state <%s> doesn't have a suitable entry point (event <%s>)",
1070
                                  getStateName(fromState).c_str(),
1071
                                  getEventName(event.type).c_str());
660✔
1072
            }
1073
        }
660✔
1074
    } else if (TransitionBehavior::FORCED == event.transitionType) {
250✔
1075
        HSM_TRACE_DEBUG("forced history transitions: %d", SC2INT(event.forcedTransitionsInfo->size()));
250✔
1076
        matchingTransitions = *event.forcedTransitionsInfo;
250✔
1077
        isCorrectTransition = true;
1078
    } else {
1079
        // NOTE: do nothing
1080
    }
1081

1082
    // ========================================================
1083
    // handle transition if it passed validation and has a target state
1084
    if (true == isCorrectTransition) {
684,098✔
1085
        bool isExitAllowed = true;
683,668✔
1086
        std::list<StateID_t> exitedStates;
683,668✔
1087

1088
        // execute self transitions first
1089
        for (auto it = matchingTransitions.begin(); it != matchingTransitions.end(); ++it) {
1,367,606✔
1090
            if ((it->fromState == it->destinationState) && (TransitionType::INTERNAL_TRANSITION == it->transitionType)) {
683,938✔
1091
                // TODO: separate type for self transition?
1092
                logHsmAction(HsmLogAction::TRANSITION, it->fromState, it->destinationState, event.type, false, event.args);
50✔
1093

1094
                // NOTE: false-positive. std::function has a bool() operator
1095
                // cppcheck-suppress misra-c2012-14.4
1096
                if (it->onTransition) {
50✔
1097
                    it->onTransition(event.args);
683,938✔
1098
                }
1099

1100
                res = HsmEventStatus_t::DONE_OK;
1101
            }
1102
        }
1103

1104
        // execute exit transition (only once in case of parallel transitions)
1105
        for (auto it = matchingTransitions.begin(); it != matchingTransitions.end(); ++it) {
1,367,606✔
1106
            // everything except internal self-transitions
1107
            if ((it->fromState != it->destinationState) || (TransitionType::EXTERNAL_TRANSITION == it->transitionType)) {
683,938✔
1108
                // exit active states only during regular transitions
1109
                if (TransitionBehavior::REGULAR == event.transitionType) {
683,888✔
1110
                    // it's an outer transition from parent state. we need to find and exit all active substates
1111
                    for (auto itActiveState = mActiveStates.rbegin(); itActiveState != mActiveStates.rend(); ++itActiveState) {
1,366,746✔
1112
                        HSM_TRACE_DEBUG("OUTER EXIT: FROM=%s, ACTIVE=%s",
1113
                                        getStateName(it->fromState).c_str(),
1114
                                        getStateName(*itActiveState).c_str());
683,838✔
1115
                        if ((it->fromState == *itActiveState) || (true == isSubstateOf(it->fromState, *itActiveState))) {
683,838✔
1116
                            isExitAllowed = onStateExiting(*itActiveState);
683,238✔
1117

1118
                            if (true == isExitAllowed) {
683,238✔
1119
                                exitedStates.push_back(*itActiveState);
683,818✔
1120
                            } else {
1121
                                break;
1122
                            }
1123
                        }
1124
                    }
1125

1126
                    // if no one blocked ongoing transition - remove child states from active list
1127
                    if (true == isExitAllowed) {
682,928✔
1128
                        // store history for states between "fromState" ----> "it->fromState"
1129
                        updateHistory(it->fromState, exitedStates);
682,908✔
1130

1131
                        for (auto itState = exitedStates.begin(); itState != exitedStates.end(); ++itState) {
1,366,286✔
1132
                            mActiveStates.remove(*itState);
683,378✔
1133
                        }
1134
                    }
1135
                    // if one of the states blocked ongoing transition we need to rollback
1136
                    else {
1137
                        for (auto itState = exitedStates.begin(); itState != exitedStates.end(); ++itState) {
30✔
1138
                            mActiveStates.remove(*itState);
10✔
1139
                            // to prevent infinite loops we don't allow state to cancel transition
1140
                            (void)onStateEntering(*itState, VariantVector_t());
10✔
1141
                            mActiveStates.push_back(*itState);
10✔
1142
                            onStateChanged(*itState, VariantVector_t());
10✔
1143
                        }
1144
                    }
1145
                }
1146
            }
1147
        }
1148

1149
        // proceed if transition was not blocked during state exit
1150
        if (true == isExitAllowed) {
683,668✔
1151
            for (auto it = matchingTransitions.begin(); it != matchingTransitions.end(); ++it) {
1,367,566✔
1152
                // everything except internal self-transitions
1153
                if ((it->fromState != it->destinationState) || (TransitionType::EXTERNAL_TRANSITION == it->transitionType)) {
683,918✔
1154
                    // NOTE: Decide if we need functionality to cancel ongoing transition
1155
                    logHsmAction(
683,868✔
1156
                        ((TransitionBehavior::ENTRYPOINT != event.transitionType) ? HsmLogAction::TRANSITION
683,868✔
1157
                                                                                  : HsmLogAction::TRANSITION_ENTRYPOINT),
1158
                        it->fromState,
683,868✔
1159
                        it->destinationState,
683,868✔
1160
                        event.type,
683,868✔
1161
                        false,
1162
                        event.args);
683,868✔
1163

1164
                    // NOTE: false-positive. std::shated_ptr has a bool() operator
1165
                    // cppcheck-suppress misra-c2012-14.4
1166
                    if (it->onTransition) {
683,868✔
1167
                        it->onTransition(event.args);
180✔
1168
                    }
1169

1170
                    if (true == onStateEntering(it->destinationState, event.args)) {
683,868✔
1171
                        std::list<StateID_t> entryPoints;
683,848✔
1172

1173
                        if (true == replaceActiveState(fromState, it->destinationState)) {
683,848✔
1174
                            onStateChanged(it->destinationState, event.args);
683,818✔
1175
                        }
1176

1177
                        // check if current state is a final state
1178
                        const auto itFinalStateEvent = mFinalStates.find(it->destinationState);
683,848✔
1179

1180
                        if (itFinalStateEvent != mFinalStates.end()) {
683,848✔
1181
                            StateID_t parentState = INVALID_HSM_STATE_ID;
150✔
1182

1183
                            // don't generate events for top level final states since no one can process them
1184
                            if (true == getParentState(it->destinationState, parentState)) {
150✔
1185
                                PendingEventInfo finalStateEvent;
140✔
1186

1187
                                finalStateEvent.transitionType = TransitionBehavior::REGULAR;
140✔
1188
                                finalStateEvent.args = event.args;
140✔
1189

1190
                                if (INVALID_HSM_EVENT_ID != itFinalStateEvent->second) {
140✔
1191
                                    finalStateEvent.type = itFinalStateEvent->second;
90✔
1192
                                } else {
1193
                                    finalStateEvent.type = event.type;
50✔
1194
                                }
1195

1196
                                {
140✔
1197
                                    HSM_SYNC_EVENTS_QUEUE();
140✔
1198
                                    mPendingEvents.push_front(finalStateEvent);
140✔
1199
                                }
140✔
1200
                            }
140✔
1201

1202
                            res = HsmEventStatus_t::DONE_OK;
150✔
1203
                        } else {
1204
                            // check if we transitioned into history state
1205
                            auto itHistoryData = mHistoryData.find(it->destinationState);
683,698✔
1206

1207
                            if (itHistoryData != mHistoryData.end()) {
683,698✔
1208
                                HSM_TRACE_DEBUG("state=<%s> is a history state with %ld stored states",
1209
                                                getStateName(it->destinationState).c_str(),
1210
                                                itHistoryData->second.previousActiveStates.size());
100✔
1211

1212
                                // transition to previous states
1213
                                if (itHistoryData->second.previousActiveStates.empty() == false) {
100✔
1214
                                    PendingEventInfo historyTransitionEvent = event;
60✔
1215

1216
                                    historyTransitionEvent.transitionType = TransitionBehavior::FORCED;
60✔
1217
                                    historyTransitionEvent.forcedTransitionsInfo =
60✔
1218
                                        std::make_shared<std::list<TransitionInfo>>();
60✔
1219

1220
                                    auto itPrevChildState = itHistoryData->second.previousActiveStates.end();
60✔
1221

1222
                                    {
60✔
1223
                                        HSM_SYNC_EVENTS_QUEUE();
60✔
1224

1225
                                        for (auto itPrevState = itHistoryData->second.previousActiveStates.begin();
60✔
1226
                                             itPrevState != itHistoryData->second.previousActiveStates.end();
220✔
1227
                                             ++itPrevState) {
220✔
1228
                                            if ((itPrevChildState != itHistoryData->second.previousActiveStates.end()) &&
260✔
1229
                                                (true == isSubstateOf(*itPrevState, *itPrevChildState))) {
100✔
1230
                                                if (false == historyTransitionEvent.forcedTransitionsInfo->empty()) {
60✔
1231
                                                    mPendingEvents.push_front(historyTransitionEvent);
60✔
1232
                                                }
1233

1234
                                                historyTransitionEvent.forcedTransitionsInfo =
60✔
1235
                                                    std::make_shared<std::list<TransitionInfo>>();
60✔
1236
                                                historyTransitionEvent.ignoreEntryPoints = true;
60✔
1237
                                            } else {
1238
                                                historyTransitionEvent.ignoreEntryPoints = false;
100✔
1239
                                            }
1240

1241
                                            itPrevChildState = itPrevState;
160✔
1242
                                            historyTransitionEvent.forcedTransitionsInfo->emplace_back(
160✔
1243
                                                it->destinationState,
160✔
1244
                                                *itPrevState,
160✔
1245
                                                TransitionType::EXTERNAL_TRANSITION,
320✔
1246
                                                nullptr,
320✔
1247
                                                nullptr);
320✔
1248
                                        }
1249

1250
                                        mPendingEvents.push_front(historyTransitionEvent);
60✔
1251
                                    }
60✔
1252

1253
                                    itHistoryData->second.previousActiveStates.clear();
60✔
1254

1255
                                    StateID_t historyParent;
60✔
1256

1257
                                    if (true == getHistoryParent(it->destinationState, historyParent)) {
60✔
1258
                                        historyTransitionEvent.forcedTransitionsInfo =
60✔
1259
                                            std::make_shared<std::list<TransitionInfo>>();
60✔
1260
                                        historyTransitionEvent.forcedTransitionsInfo->emplace_back(
60✔
1261
                                            it->destinationState,
60✔
1262
                                            historyParent,
1263
                                            TransitionType::EXTERNAL_TRANSITION,
120✔
1264
                                            nullptr,
120✔
1265
                                            nullptr);
60✔
1266
                                        historyTransitionEvent.ignoreEntryPoints = true;
60✔
1267

1268
                                        HSM_SYNC_EVENTS_QUEUE();
60✔
1269
                                        mPendingEvents.push_front(historyTransitionEvent);
60✔
1270
                                    }
60✔
1271
                                }
60✔
1272
                                // transition to default state or entry point
1273
                                else {
1274
                                    std::list<StateID_t> historyTargets;
40✔
1275
                                    StateID_t historyParent;
40✔
1276

1277
                                    if (true == getHistoryParent(it->destinationState, historyParent)) {
40✔
1278
                                        HSM_TRACE_DEBUG("found parent=<%s> for history state=<%s>",
1279
                                                        getStateName(historyParent).c_str(),
1280
                                                        getStateName(it->destinationState).c_str());
40✔
1281

1282
                                        if (INVALID_HSM_STATE_ID == itHistoryData->second.defaultTarget) {
40✔
1283
                                            // transition to parent's entry point if there is no default history target
1284
                                            historyTargets.push_back(historyParent);
10✔
1285
                                        } else {
1286
                                            historyTargets.push_back(itHistoryData->second.defaultTarget);
30✔
1287
                                            historyTargets.push_back(historyParent);
30✔
1288
                                        }
1289
                                    } else {
1290
                                        HSM_TRACE_ERROR("parent for history state=<%s> wasnt found",
1291
                                                        getStateName(it->destinationState).c_str());
40✔
1292
                                    }
1293

1294
                                    PendingEventInfo defHistoryTransitionEvent = event;
40✔
1295

1296
                                    defHistoryTransitionEvent.transitionType = TransitionBehavior::FORCED;
40✔
1297

1298
                                    for (const StateID_t historyTargetState : historyTargets) {
110✔
1299
                                        HsmTransitionCallback_t cbTransition;
70✔
1300

1301
                                        defHistoryTransitionEvent.forcedTransitionsInfo =
70✔
1302
                                            std::make_shared<std::list<TransitionInfo>>();
70✔
1303

1304
                                        if ((INVALID_HSM_STATE_ID != itHistoryData->second.defaultTarget) &&
70✔
1305
                                            (historyTargetState == historyParent)) {
60✔
1306
                                            defHistoryTransitionEvent.ignoreEntryPoints = true;
30✔
1307
                                        } else {
1308
                                            cbTransition = itHistoryData->second.defaultTargetTransitionCallback;
40✔
1309
                                        }
1310

1311
                                        defHistoryTransitionEvent.forcedTransitionsInfo->emplace_back(
70✔
1312
                                            it->destinationState,
70✔
1313
                                            historyTargetState,
1314
                                            TransitionType::EXTERNAL_TRANSITION,
140✔
1315
                                            cbTransition,
1316
                                            nullptr);
70✔
1317

1318
                                        mPendingEvents.push_front(defHistoryTransitionEvent);
70✔
1319
                                    }
70✔
1320
                                }
80✔
1321

1322
                                res = HsmEventStatus_t::PENDING;
1323
                            }
1324
                            // check if new state has substates and initiate entry transition
1325
                            else if ((false == event.ignoreEntryPoints) &&
1,367,046✔
1326
                                     (true == getEntryPoints(it->destinationState, event.type, event.args, entryPoints))) {
683,448✔
1327
                                HSM_TRACE_DEBUG("state <%s> has substates with %d entry points (first: <%s>)",
1328
                                                getStateName(it->destinationState).c_str(),
1329
                                                SC2INT(entryPoints.size()),
1330
                                                getStateName(entryPoints.front()).c_str());
500✔
1331
                                PendingEventInfo entryPointTransitionEvent = event;
500✔
1332

1333
                                entryPointTransitionEvent.transitionType = TransitionBehavior::ENTRYPOINT;
500✔
1334

1335
                                {
500✔
1336
                                    HSM_SYNC_EVENTS_QUEUE();
500✔
1337
                                    mPendingEvents.push_front(entryPointTransitionEvent);
500✔
1338
                                }
500✔
1339
                                res = HsmEventStatus_t::PENDING;
500✔
1340
                            } else {
500✔
1341
                                if (true == event.ignoreEntryPoints) {
683,098✔
1342
                                    HSM_TRACE_DEBUG(
1343
                                        "entry points were forcefully ignored (probably due to history transition)");
1344
                                    res = HsmEventStatus_t::PENDING;
1345
                                } else {
1346
                                    res = HsmEventStatus_t::DONE_OK;
682,948✔
1347
                                }
1348
                            }
1349
                        }
1350
                    } else {
683,848✔
1351
                        for (auto itState = exitedStates.begin(); itState != exitedStates.end(); ++itState) {
40✔
1352
                            // to prevent infinite loops we don't allow state to cancel transition
1353
                            (void)onStateEntering(*itState, VariantVector_t());
20✔
1354
                            (void)addActiveState(*itState);
20✔
1355
                            onStateChanged(*itState, VariantVector_t());
20✔
1356
                        }
1357
                    }
1358
                }
1359
            }
1360
        } else {
1361
            res = HsmEventStatus_t::CANCELED;
1362
        }
1363
    }
683,668✔
1364

1365
    if (HsmEventStatus_t::DONE_FAILED == res) {
684,348✔
1366
        HSM_TRACE_DEBUG("event <%s> in state <%s> was ignored.",
1367
                        getEventName(event.type).c_str(),
1368
                        getStateName(fromState).c_str());
1369
    }
1370

1371
    DEBUG_DUMP_ACTIVE_STATES();
684,348✔
1372
    HSM_TRACE_CALL_RESULT("%d", SC2INT(res));
684,348✔
1373
    return res;
684,348✔
1374
}
684,348✔
1375

1376
void HierarchicalStateMachine::Impl::clearPendingEvents() {
10✔
1377
    HSM_TRACE_CALL_DEBUG_ARGS("clearPendingEvents: mPendingEvents.size()=%ld", mPendingEvents.size());
10✔
1378

1379
    for (auto it = mPendingEvents.begin(); (it != mPendingEvents.end()); ++it) {
10✔
1380
        // since ongoing transitions can't be canceled we need to treat entry point transitions as atomic
1381
        if (TransitionBehavior::REGULAR == it->transitionType) {
×
1382
            it->releaseLock();
×
1383
        }
1384
    }
1385

1386
    mPendingEvents.clear();
10✔
1387
}
10✔
1388

1389
// ============================================================================
1390
// PRIVATE: PendingEventInfo
1391
// ============================================================================
1392
HierarchicalStateMachine::Impl::PendingEventInfo::~PendingEventInfo() {
2,614,004✔
1393
    if (true == cvLock.unique()) {
2,615,414✔
1394
        HSM_TRACE_CALL_DEBUG_ARGS("event=<%d> was deleted. releasing lock", SC2INT(type));
1,410✔
1395
        unlock(HsmEventStatus_t::DONE_FAILED);
1,410✔
1396
        cvLock.reset();
1,410✔
1397
        syncProcessed.reset();
1,410✔
1398
    }
1399
}
2,630,549✔
1400

1401
void HierarchicalStateMachine::Impl::PendingEventInfo::initLock() {
1,410✔
1402
    if (!cvLock) {
1,410✔
1403
        cvLock = std::make_shared<Mutex>();
1,410✔
1404
        syncProcessed = std::make_shared<ConditionVariable>();
1,410✔
1405
        transitionStatus = std::make_shared<HsmEventStatus_t>();
1,410✔
1406
        *transitionStatus = HsmEventStatus_t::PENDING;
1,410✔
1407
    }
1408
}
1,410✔
1409

1410
void HierarchicalStateMachine::Impl::PendingEventInfo::releaseLock() {
×
1411
    if (true == isSync()) {
×
1412
        HSM_TRACE_CALL_DEBUG_ARGS("releaseLock");
×
1413
        unlock(HsmEventStatus_t::DONE_FAILED);
×
1414
        cvLock.reset();
×
1415
        syncProcessed.reset();
×
1416
    }
1417
}
×
1418

1419
bool HierarchicalStateMachine::Impl::PendingEventInfo::isSync() {
686,585✔
1420
    return (nullptr != cvLock);
686,585✔
1421
}
1422

1423
void HierarchicalStateMachine::Impl::PendingEventInfo::wait(const int timeoutMs) {
1,410✔
1424
    if (true == isSync()) {
1,410✔
1425
        // NOTE: lock is needed only because we have to use cond variable
1426
        UniqueLock lck(*cvLock);
1,410✔
1427

1428
        HSM_TRACE_CALL_DEBUG_ARGS("trying to wait... (current status=%d, %p)",
1429
                                  SC2INT(*transitionStatus),
1430
                                  transitionStatus.get());
1,410✔
1431
        if (timeoutMs > 0) {
1,410✔
1432
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
1433
            // cppcheck-suppress [misra-c2012-15.5, misra-c2012-17.7]
1434
            syncProcessed->wait_for(lck, timeoutMs, [=]() { return (HsmEventStatus_t::PENDING != *transitionStatus); });
20✔
1435
        } else {
1436
            // NOTE: false-positive. "return" statement belongs to lambda function, not parent function
1437
            // cppcheck-suppress [misra-c2012-15.5, misra-c2012-17.7]
1438
            syncProcessed->wait(lck, [=]() { return (HsmEventStatus_t::PENDING != *transitionStatus); });
2,800✔
1439
        }
1440

1441
        HSM_TRACE_DEBUG("unlocked! transitionStatus=%d", SC2INT(*transitionStatus));
1,410✔
1442
    }
1,410✔
1443
}
1,410✔
1444

1445
void HierarchicalStateMachine::Impl::PendingEventInfo::unlock(const HsmEventStatus_t status) {
685,175✔
1446
    HSM_TRACE_CALL_DEBUG_ARGS("try to unlock with status=%d", SC2INT(status));
685,175✔
1447

1448
    if (true == isSync()) {
685,175✔
1449
        HSM_TRACE_DEBUG("SYNC object (%p)", transitionStatus.get());
3,550✔
1450
        *transitionStatus = status;
3,550✔
1451

1452
        if (status != HsmEventStatus_t::PENDING) {
3,550✔
1453
            syncProcessed->notify();
2,820✔
1454
        }
1455
    } else {
1456
        HSM_TRACE_DEBUG("ASYNC object");
685,175✔
1457
    }
1458
}
685,175✔
1459

1460
bool HierarchicalStateMachine::Impl::hasSubstates(const StateID_t parent) const {
683,518✔
1461
    return (mSubstates.find(parent) != mSubstates.end());
683,518✔
1462
}
1463

1464
bool HierarchicalStateMachine::Impl::hasEntryPoint(const StateID_t state) const {
520✔
1465
    return (mSubstateEntryPoints.find(state) != mSubstateEntryPoints.end());
520✔
1466
}
1467

1468
bool HierarchicalStateMachine::Impl::getEntryPoints(const StateID_t state,
686,598✔
1469
                                                    const EventID_t onEvent,
1470
                                                    const VariantVector_t& transitionArgs,
1471
                                                    std::list<StateID_t>& outEntryPoints) const {
1472
    auto itRange = mSubstateEntryPoints.equal_range(state);
686,598✔
1473

1474
    outEntryPoints.clear();
686,598✔
1475

1476
    for (auto it = itRange.first; it != itRange.second; ++it) {
688,698✔
1477
        if (((INVALID_HSM_EVENT_ID == it->second.onEvent) || (onEvent == it->second.onEvent)) &&
2,100✔
1478
            // check transition condition if it was defined
1479
            ((nullptr == it->second.checkCondition) ||
2,040✔
1480
             (it->second.checkCondition(transitionArgs) == it->second.expectedConditionValue))) {
120✔
1481
            outEntryPoints.push_back(it->second.state);
2,100✔
1482
        }
1483
    }
1484

1485
    return (false == outEntryPoints.empty());
686,598✔
1486
}
1487

1488
bool HierarchicalStateMachine::Impl::replaceActiveState(const StateID_t oldState, const StateID_t newState) {
683,848✔
1489
    HSM_TRACE_CALL_DEBUG_ARGS("oldState=<%s>, newState=<%s>", getStateName(oldState).c_str(), getStateName(newState).c_str());
683,848✔
1490

1491
    if (false == isSubstateOf(oldState, newState)) {
683,848✔
1492
        mActiveStates.remove(oldState);
682,958✔
1493
    }
1494

1495
    return addActiveState(newState);
683,848✔
1496
}
1497

1498
bool HierarchicalStateMachine::Impl::addActiveState(const StateID_t newState) {
683,868✔
1499
    HSM_TRACE_CALL_DEBUG_ARGS("newState=<%s>", getStateName(newState).c_str());
683,868✔
1500
    bool wasAdded = false;
683,868✔
1501

1502
    if (false == isStateActive(newState)) {
683,868✔
1503
        mActiveStates.push_back(newState);
683,838✔
1504
        wasAdded = true;
683,838✔
1505
    }
1506

1507
    HSM_TRACE_DEBUG("mActiveStates.size=%d", SC2INT(mActiveStates.size()));
683,868✔
1508
    return wasAdded;
683,868✔
1509
}
1510

1511
#ifdef HSM_ENABLE_SAFE_STRUCTURE
1512

1513
bool HierarchicalStateMachine::Impl::isTopState(const StateID_t state) const {
6,910✔
1514
    auto it = std::find(mTopLevelStates.begin(), mTopLevelStates.end(), state);
6,910✔
1515

1516
    return (it == mTopLevelStates.end());
6,910✔
1517
}
1518

1519
bool HierarchicalStateMachine::Impl::isSubstate(const StateID_t state) const {
5,420✔
1520
    bool result = false;
5,420✔
1521

1522
    for (auto itSubstate = mSubstates.begin(); itSubstate != mSubstates.end(); ++itSubstate) {
5,420✔
1523
        if (itSubstate->second == state) {
×
1524
            result = true;
1525
            break;
1526
        }
1527
    }
1528

1529
    return result;
5,420✔
1530
}
1531

1532
bool HierarchicalStateMachine::Impl::hasParentState(const StateID_t state, StateID_t& outParent) const {
3,490✔
1533
    bool hasParent = false;
3,490✔
1534

1535
    for (auto it = mSubstates.begin(); it != mSubstates.end(); ++it) {
7,290✔
1536
        if (state == it->second) {
4,280✔
1537
            hasParent = true;
480✔
1538
            outParent = it->first;  // cppcheck-suppress misra-c2012-17.8 ; outParent is used to return result
480✔
1539
            break;
480✔
1540
        }
1541
    }
1542

1543
    return hasParent;
3,490✔
1544
}
1545

1546
#endif  // HSM_ENABLE_SAFE_STRUCTURE
1547

1548
bool HierarchicalStateMachine::Impl::enableHsmDebugging() {
×
1549
#ifdef HSMBUILD_DEBUGGING
1550
    char* envPath = std::getenv(ENV_DUMPPATH);
×
1551

1552
    return enableHsmDebugging((nullptr == envPath) ? DEFAULT_DUMP_PATH : std::string(envPath));
×
1553
#else
1554
    return true;
1555
#endif
1556
}
1557

1558
bool HierarchicalStateMachine::Impl::enableHsmDebugging(const std::string& dumpPath) {
×
1559
#ifdef HSMBUILD_DEBUGGING
1560
    bool res = false;
×
1561
    bool isNewLog = (access(dumpPath.c_str(), F_OK) != 0);
×
1562

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

1566
        if (true == isNewLog) {
×
1567
            *mHsmLog << "---\n";
×
1568
            mHsmLog->flush();
×
1569
        }
1570

1571
        res = true;
1572
    }
1573

1574
    return res;
×
1575
#else
1576
    return true;
1577
#endif
1578
}
1579

1580
void HierarchicalStateMachine::Impl::disableHsmDebugging() {
3,980✔
1581
#ifdef HSMBUILD_DEBUGGING
1582
    mHsmLogFile.close();
3,980✔
1583
#endif
1584
}
3,980✔
1585

1586
void HierarchicalStateMachine::Impl::logHsmAction(const HsmLogAction action,
2,053,604✔
1587
                                                  const StateID_t fromState,
1588
                                                  const StateID_t targetState,
1589
                                                  const EventID_t event,
1590
                                                  const bool hasFailed,
1591
                                                  const VariantVector_t& args) {
1592
#ifdef HSMBUILD_DEBUGGING
1593
    if (true == mHsmLogFile.is_open()) {
2,053,604✔
1594
        static const std::map<HsmLogAction, std::string> actionsMap = {
×
1595
            std::make_pair(HsmLogAction::IDLE, "idle"),
×
1596
            std::make_pair(HsmLogAction::TRANSITION, "transition"),
×
1597
            std::make_pair(HsmLogAction::TRANSITION_ENTRYPOINT, "transition_entrypoint"),
×
1598
            std::make_pair(HsmLogAction::CALLBACK_EXIT, "callback_exit"),
×
1599
            std::make_pair(HsmLogAction::CALLBACK_ENTER, "callback_enter"),
×
1600
            std::make_pair(HsmLogAction::CALLBACK_STATE, "callback_state"),
×
1601
            std::make_pair(HsmLogAction::ON_ENTER_ACTIONS, "onenter_actions"),
×
1602
            std::make_pair(HsmLogAction::ON_EXIT_ACTIONS, "onexit_actions")};
×
1603
        char bufTime[80] = {0};
×
1604
        char bufTimeMs[6] = {0};
×
1605
        auto currentTimePoint = std::chrono::system_clock::now();
×
1606
        const std::time_t tt = std::chrono::system_clock::to_time_t(currentTimePoint);
×
1607
        std::tm timeinfo;
×
1608
        const std::tm* tmResult = nullptr;  // this is just to check that localtime was executed correctly
×
1609

1610
  #ifdef WIN32
1611
        if (0 == ::localtime_s(&timeinfo, &tt)) {
1612
            tmResult = &timeinfo;
1613
        }
1614
  #else
1615
        tmResult = localtime(&tt);
×
1616
        if (nullptr != tmResult) {
×
1617
            timeinfo = *tmResult;
×
1618
        }
1619
  #endif  // WIN32
1620

1621
        if (nullptr != tmResult) {
×
1622
            (void)std::strftime(bufTime, sizeof(bufTime), "%Y-%m-%d %H:%M:%S", &timeinfo);
×
1623
            (void)snprintf(
×
1624
                bufTimeMs,
1625
                sizeof(bufTimeMs),
1626
                ".%03d",
1627
                static_cast<int>(
1628
                    std::chrono::duration_cast<std::chrono::milliseconds>(currentTimePoint.time_since_epoch()).count() % 1000));
×
1629
        } else {
1630
            (void)std::strcpy(bufTime, "0000-00-00 00:00:00");
×
1631
            (void)std::strcpy(bufTimeMs, ".000");
×
1632
        }
1633

1634
        *mHsmLog << "\n-\n"
×
1635
                    "  timestamp: \""
1636
                 << bufTime << bufTimeMs
1637
                 << "\"\n"
1638
                    "  active_states:";
×
1639

1640
        for (auto itState = mActiveStates.begin(); itState != mActiveStates.end(); ++itState) {
×
1641
            *mHsmLog << "\n    - \"" << getStateName(*itState) << "\"";
×
1642
        }
1643

1644
        *mHsmLog << "\n  action: " << actionsMap.at(action)
×
1645
                 << "\n"
1646
                    "  from_state: \""
1647
                 << getStateName(fromState)
×
1648
                 << "\"\n"
1649
                    "  target_state: \""
1650
                 << getStateName(targetState)
×
1651
                 << "\"\n"
1652
                    "  event: \""
1653
                 << getEventName(event)
×
1654
                 << "\"\n"
1655
                    "  status: "
1656
                 << (hasFailed ? "failed" : "")
1657
                 << "\n"
1658
                    "  args:";
×
1659

1660
        for (auto itArg = args.begin(); itArg != args.end(); ++itArg) {
×
1661
            *mHsmLog << "\n    - " << itArg->toString();
×
1662
        }
1663

1664
        mHsmLog->flush();
×
1665
    }
1666
#endif  // HSMBUILD_DEBUGGING
1667
}
2,053,604✔
1668

1669
#ifndef HSM_DISABLE_DEBUG_TRACES
1670
void HierarchicalStateMachine::Impl::dumpActiveStates() {
1,368,696✔
1671
    HSM_TRACE_CALL();
1,368,696✔
1672

1673
    std::string temp;
1,368,696✔
1674

1675
    for (auto it = mActiveStates.begin(); it != mActiveStates.end(); ++it) {
2,742,922✔
1676
        temp += getStateName(*it) + std::string(", ");
2,748,452✔
1677
    }
1678

1679
    HSM_TRACE_DEBUG("active states: <%s>", temp.c_str());
1,368,696✔
1680
}
1,368,696✔
1681

1682
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
1,374,226✔
1683
    std::string res;
1,374,226✔
1684
  #ifndef HSM_DISABLE_THREADSAFETY
1685
    LockGuard lk(mParentSync);
1,374,226✔
1686
  #endif
1687

1688
    if (nullptr != mParent) {
1,374,226✔
1689
        res = mParent->getStateName(state);
1,374,042✔
1690
    }
1691

1692
    return res;
1,374,226✔
1693
}
1,374,226✔
1694

1695
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
×
1696
    std::string res;
×
1697
  #ifndef HSM_DISABLE_THREADSAFETY
1698
    LockGuard lk(mParentSync);
×
1699
  #endif
1700

1701
    if (nullptr != mParent) {
×
1702
        res = mParent->getEventName(event);
×
1703
    }
1704

1705
    return res;
×
1706
}
×
1707
#else   // HSM_DISABLE_DEBUG_TRACES
1708

1709
std::string HierarchicalStateMachine::Impl::getStateName(const StateID_t state) {
1710
    return std::string();
1711
}
1712

1713
std::string HierarchicalStateMachine::Impl::getEventName(const EventID_t event) {
1714
    return std::string();
1715
}
1716
#endif  // HSM_DISABLE_DEBUG_TRACES
1717

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