|  | // Copyright (c) 2014 Google Inc. All rights reserved. | 
|  | // Use of this source code is governed by a BSD-style license that can be | 
|  | // found in the LICENSE file. | 
|  |  | 
|  | #include "base/state_machine_shell.h" | 
|  |  | 
|  | #include "base/logging.h" | 
|  |  | 
|  | // --- Macros for common logging idioms --- | 
|  |  | 
|  | #define SM_STATE(s) GetStateString(s) << "(" << s << ")" | 
|  | #define SM_STATEN(s) GetStateString(s) << "(" << s.value_or(0) << ")" | 
|  | #define SM_EVENT(e) GetEventString(e) << "(" << e << ")" | 
|  | #define SM_PREFIX \ | 
|  | name_ << "-" << SM_STATEN(state_) << "v" << version_ << ": " | 
|  | #define SM_DLOG(level) DLOG(level) << SM_PREFIX | 
|  |  | 
|  | namespace base { | 
|  |  | 
|  | StateMachineBaseShell::StateMachineBaseShell(const std::string &name) | 
|  | : name_(name), | 
|  | version_(0), | 
|  | is_handling_(false), | 
|  | is_initialized_(false), | 
|  | should_log_(false) { | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::Initialize() { | 
|  | if (is_initialized_) { | 
|  | return; | 
|  | } | 
|  |  | 
|  | if (should_log_) { | 
|  | SM_DLOG(INFO) << "INITIALIZING"; | 
|  | } | 
|  | DCHECK(!state_) << SM_PREFIX; | 
|  | DCHECK(!GetParentState(GetUserInitialState())) << SM_PREFIX; | 
|  | is_initialized_ = true; | 
|  | FollowInitialSubstates(); | 
|  | } | 
|  |  | 
|  | bool StateMachineBaseShell::IsIn(State state) const { | 
|  | StateN currentState = state_; | 
|  | while (currentState) { | 
|  | if (state == currentState.value()) { | 
|  | return true; | 
|  | } | 
|  |  | 
|  | currentState = GetParentState(currentState); | 
|  | } | 
|  |  | 
|  | return false; | 
|  | } | 
|  |  | 
|  | const char *StateMachineBaseShell::GetStateString(StateN state) const { | 
|  | if (!state) { | 
|  | return "<none>"; | 
|  | } | 
|  |  | 
|  | const char *user = GetUserStateString(state.value()); | 
|  | if (user) { | 
|  | return user; | 
|  | } | 
|  |  | 
|  | return "UNKNOWN_STATE"; | 
|  | } | 
|  |  | 
|  | const char *StateMachineBaseShell::GetEventString(EventN event) const { | 
|  | if (!event) { | 
|  | return "<none>"; | 
|  | } | 
|  |  | 
|  | const char *user = GetUserEventString(event.value()); | 
|  | if (user) { | 
|  | return user; | 
|  | } | 
|  |  | 
|  | return "UNKNOWN_EVENT"; | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::Handle(Event event, void *data) { | 
|  | Initialize(); | 
|  |  | 
|  | if (is_handling_) { | 
|  | if (should_log_) { | 
|  | SM_DLOG(INFO) << "QUEUEING " << SM_EVENT(event); | 
|  | } | 
|  |  | 
|  | event_queue_.push(Bind(&StateMachineBaseShell::HandleOneEvent, Unretained(this), | 
|  | event, Unretained(data))); | 
|  | return; | 
|  | } | 
|  |  | 
|  | HandleOneEvent(event, data); | 
|  | HandleQueuedEvents(); | 
|  | } | 
|  |  | 
|  | StateMachineBaseShell::StateN | 
|  | StateMachineBaseShell::GetParentState(StateN state) const { | 
|  | if (!state) { | 
|  | return StateN(); | 
|  | } | 
|  |  | 
|  | return GetUserParentState(state.value()); | 
|  | } | 
|  |  | 
|  | StateMachineBaseShell::StateN | 
|  | StateMachineBaseShell::GetInitialSubstate(StateN state) const { | 
|  | if (!state) { | 
|  | return StateN(); | 
|  | } | 
|  |  | 
|  | return GetUserInitialSubstate(state.value()); | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::HandleQueuedEvents() { | 
|  | while (!event_queue_.empty()) { | 
|  | event_queue_.front().Run(); | 
|  | event_queue_.pop(); | 
|  | } | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::HandleOneEvent(Event event, void *data) { | 
|  | if (should_log_) { | 
|  | SM_DLOG(INFO) << "HANDLING " << SM_EVENT(event); | 
|  | } | 
|  |  | 
|  | DCHECK(!is_handling_) << SM_PREFIX; | 
|  | is_handling_ = true; | 
|  |  | 
|  | // Walk up the hierarchy from the simplest current state, looking for event | 
|  | // handlers, stopping at the first state that decides to handle the event. | 
|  | StateN source = state_; | 
|  | Result result; | 
|  | while (source) { | 
|  | result = HandleUserStateEvent(source.value(), event, data); | 
|  | if (!result.is_handled) { | 
|  | // Result was not handled, so we continue up the state chain. | 
|  | source = GetParentState(source); | 
|  | continue; | 
|  | } | 
|  |  | 
|  | break; | 
|  | } | 
|  |  | 
|  | // Log that the event was completely unhandled, because that's kinda weird. | 
|  | // It's better if the state machine handler explicitly consumes and ignores | 
|  | // events that should be ignored. | 
|  | if (!result.is_handled && should_log_) { | 
|  | SM_DLOG(WARNING) << "Event " << SM_EVENT(event) << " was unhandled."; | 
|  | } | 
|  |  | 
|  | // If a transition was triggered, execute it. | 
|  | if (result.is_transition) { | 
|  | Transition(event, source.value(), result.target.value(), | 
|  | result.is_external); | 
|  | } | 
|  |  | 
|  | is_handling_ = false; | 
|  | } | 
|  |  | 
|  | // Returns the index of |state| in |path|, or -1 if not found in |path_length| | 
|  | // elements. If |state| is null, then it will always return -1. | 
|  | static int IndexOf(StateMachineBaseShell::StateN state, | 
|  | const StateMachineBaseShell::State *path, | 
|  | size_t path_length) { | 
|  | if (!state) { | 
|  | return -1; | 
|  | } | 
|  |  | 
|  | int i; | 
|  | for (i = 0; !(path[i] == state) && i < path_length; ++i) { | 
|  | } | 
|  | return (i == path_length ? -1 : i); | 
|  | } | 
|  |  | 
|  | // Finds the least common ancestor between the two state paths. If there is no | 
|  | // common ancestor, returns null. | 
|  | static StateMachineBaseShell::StateN FindLeastCommonAncestor( | 
|  | const StateMachineBaseShell::State *path_a, | 
|  | size_t length_a, | 
|  | const StateMachineBaseShell::State *path_b, | 
|  | size_t length_b) { | 
|  | size_t max = std::min(length_a, length_b); | 
|  | size_t i; | 
|  | for (i = 0; path_a[i] == path_b[i] && i < max; ++i) { } | 
|  | return (i == 0 ? StateMachineBaseShell::StateN() : path_a[i - 1]); | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::GetPath(State state, size_t max_depth, State *out_path, | 
|  | size_t *out_depth) const { | 
|  | if (max_depth == 0) { | 
|  | SM_DLOG(FATAL) << "max_depth must be > 0"; | 
|  | if (out_depth) { | 
|  | *out_depth = 0; | 
|  | } | 
|  | return; | 
|  | } | 
|  |  | 
|  | size_t depth = 0; | 
|  | StateN currentState = state; | 
|  | while (currentState && depth < max_depth) { | 
|  | out_path[depth] = currentState.value(); | 
|  | ++depth; | 
|  | currentState = GetParentState(currentState); | 
|  | } | 
|  |  | 
|  | // Reverse so the path is in ascending depth order. | 
|  | std::reverse(out_path, out_path + depth); | 
|  | if (out_depth) { | 
|  | *out_depth = depth; | 
|  | } | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::Transition(Event event, State source, State target, | 
|  | bool is_external) { | 
|  | if (should_log_) { | 
|  | SM_DLOG(INFO) << SM_EVENT(event) << " caused " << SM_STATE(source) << " -> " | 
|  | << SM_STATE(target) << (is_external ? " (external)" : ""); | 
|  | } | 
|  |  | 
|  | // Define a reasonable max depth so we don't have to heap allocate. I've never | 
|  | // seen an HSM as deep as whatever arbitrary value this is defined to be at | 
|  | // the moment. | 
|  | static const size_t kMaxDepth = 16; | 
|  | State source_path[kMaxDepth]; | 
|  | size_t source_depth; | 
|  | GetPath(source, kMaxDepth, source_path, &source_depth); | 
|  | State target_path[kMaxDepth]; | 
|  | size_t target_depth; | 
|  | GetPath(target, kMaxDepth, target_path, &target_depth); | 
|  | StateN least_common_ancestor = | 
|  | FindLeastCommonAncestor(source_path, source_depth, target_path, | 
|  | target_depth); | 
|  |  | 
|  | // External transitions must exit the source state and enter the target | 
|  | // state, so if the LCA is one of the two, we need to go one level up. | 
|  | if (is_external && (least_common_ancestor == source || | 
|  | least_common_ancestor == target)) { | 
|  | least_common_ancestor = GetParentState(least_common_ancestor); | 
|  | } | 
|  |  | 
|  | // Unwind (exit) states up to the closest common ancestor. | 
|  | while (state_ && !(state_ == least_common_ancestor)) { | 
|  | ExitCurrentState(); | 
|  | } | 
|  |  | 
|  | // Update version before we wind down to the target. | 
|  | ++version_; | 
|  |  | 
|  | // Wind (enter) states down to the target state. | 
|  | size_t next_depth = IndexOf(state_, target_path, target_depth) + 1; | 
|  | DCHECK_LE(next_depth, target_depth); | 
|  | while (next_depth < target_depth) { | 
|  | EnterState(target_path[next_depth]); | 
|  | ++next_depth; | 
|  | } | 
|  |  | 
|  | FollowInitialSubstates(); | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::FollowInitialSubstates() { | 
|  | while (true) { | 
|  | StateN substate = | 
|  | (state_ ? GetInitialSubstate(state_) : GetUserInitialState()); | 
|  | if (!substate) { | 
|  | break; | 
|  | } | 
|  | EnterState(substate.value()); | 
|  | } | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::EnterState(State state) { | 
|  | state_ = state; | 
|  |  | 
|  | if (should_log_) { | 
|  | SM_DLOG(INFO) << "ENTER"; | 
|  | } | 
|  |  | 
|  | HandleUserStateEnter(state); | 
|  | } | 
|  |  | 
|  | void StateMachineBaseShell::ExitCurrentState() { | 
|  | if (should_log_) { | 
|  | SM_DLOG(INFO) << "EXIT"; | 
|  | } | 
|  |  | 
|  | HandleUserStateExit(state_.value()); | 
|  | state_ = GetParentState(state_); | 
|  | } | 
|  |  | 
|  | }  // namespace base |