# State transitions in base
baseディレクトリを埋めようの会。
本日は[StateTransitions](https://source.chromium.org/chromium/chromium/src/+/main:base/state_transitions.h;l=59;drc=e4622aaeccea84652488d1822c28c78b7115684f)。
## StateTransitions とは
完全順序のState集合に対して、そのtransitionをhandleする型。
正しいtransitionを登録することができ、それ以外の間違ったTransitionをするとinvalidであることを教えてくれる。
例えば以下のような感じ。
```cpp=
enum class State { kState1 = 0, kState2, kState3, kState4 };
// 1 -> {2 or 3}, 2 -> {3 or 4} にのみ推移してOK
const StateTransitions<State> transitions({
{State::kState1, {State::kState2, State::kState3}},
{State::kState2, {State::kState3, State::kState4}},
});
// OK: 1つめに合致
ASSERT_TRUE(transitions.IsTransitionValid(State::kState1, State::kState2));
// OK: 2つめに合致
ASSERT_TRUE(transitions.IsTransitionValid(State::kState2, State::kState3));
// BAD: 1からは2or3のみ。1->2->4のような複数での推移はvalidでないとする。
ASSERT_FALSE(transitions.IsTransitionValid(State::kState1, State::kState4));
// BAD: 3からの推移は登録されていない
ASSERT_FALSE(transitions.IsTransitionValid(State::kState3, State::kState4));
```
なおプロダクションコードでは以下のようなDCHECKを挟んであげるとstateのチェックができる。
```cpp
void DCheckStateTransition(State old_state, State new_state) {
#if DCHECK_IS_ON()
static const base::NoDestructor<StateTransitions<State>> transitions(
StateTransitions<State>({
{kState1, {kState2, kState3}},
{kState2, {kState3}},
{kState3, {}},
}));
DCHECK_STATE_TRANSITION(transitions, old_state, new_state);
#endif // DCHECK_IS_ON()
}
```
一応DCHECK時にしかチェックしないモデルなので、謎vectorのシングルトンが多少増える分には構わないということでNoDestructorで作っている。
## 中身
Stateは好きに渡すことができるテンプレートとして定義されている。
ただし、State型は以下を満たす必要がある。
- operator== が定義されている
- コピー可能
例えばenum classはこれを満たす。
```cpp
template <typename State>
struct StateTransitions {
...;
};
```
やることは簡単。
State transitionは、出発地点の`State`と目的地の`vector<State>`のペアのvectorとして保存されている。
```cpp
struct StateTransition {
StateTransition(State source, std::vector<State> destinations)
: source(std::move(source)), destinations(std::move(destinations)) {}
const State source;
const std::vector<State> destinations;
};
const std::vector<StateTransition> state_transitions;
```
そして、`source`と`destination`を受け取って、それがルールにマッチするかを確認する関数さえあれば良いのでそれを[`IsTransitionValid`](https://source.chromium.org/chromium/chromium/src/+/main:base/state_transitions.h;l=85;drc=e4622aaeccea84652488d1822c28c78b7115684f)として提供しており、これは`source`にマッチするStateTransitionをとってきてその`destination`の中にbase::Containしているかを確認するだけ。
std::vectorの中をfor文で探索するよりbase::small_mapでStateの数に応じてmapにしたりvectorにしてもらうほうが厳密には効率的だと思うが、Stateはそんなにないであろうことと、DCHECK専用のクラスであることから、ここではvectorで簡易的に実装されているんだと思う。