Last active
November 25, 2018 09:49
-
-
Save rokups/3cccf4b8830aad72f380a0b2c57e490f to your computer and use it in GitHub Desktop.
Tiny finite state machine implementation powered by preprocessor abuse.
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
// | |
// Copyright (c) 2018 Rokas Kupstys | |
// | |
// Permission is hereby granted, free of charge, to any person obtaining a copy | |
// of this software and associated documentation files (the "Software"), to deal | |
// in the Software without restriction, including without limitation the rights | |
// to use, copy, modify, merge, publish, distribute, sublicense, and/or sell | |
// copies of the Software, and to permit persons to whom the Software is | |
// furnished to do so, subject to the following conditions: | |
// | |
// The above copyright notice and this permission notice shall be included in | |
// all copies or substantial portions of the Software. | |
// | |
// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR | |
// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, | |
// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE | |
// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER | |
// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, | |
// OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN | |
// THE SOFTWARE. | |
// | |
#pragma once | |
#include <cassert> | |
enum class FSM : unsigned char | |
{ | |
/// State is being entered | |
Enter, | |
/// Internal use. "Enter" state will be completed manually. | |
EnterInProgress, | |
/// State is being updated. | |
Update, | |
/// State is being left. | |
Leave, | |
/// Internal use. "Leave" state will be completed manually. | |
LeaveInProgress, | |
/// Max number of state steps. | |
Max, | |
}; | |
#define FSM_DECLARE(EnumName) \ | |
FSM _fsm##EnumName##Step = FSM::Update; \ | |
EnumName _fsm##EnumName = (EnumName)~0U; \ | |
void Update##EnumName(); \ | |
void Next##EnumName() \ | |
{ \ | |
_fsm##EnumName##Step = static_cast<FSM>(( \ | |
static_cast<unsigned char>(_fsm##EnumName##Step) + 1) % \ | |
static_cast<unsigned char>(FSM::Max)); \ | |
} \ | |
void Set##EnumName(EnumName state) \ | |
{ \ | |
assert(_fsm##EnumName##Step == FSM::Update); \ | |
if (state == _fsm##EnumName) \ | |
return; \ | |
\ | |
Next##EnumName(); \ | |
Update##EnumName(); \ | |
_fsm##EnumName = state; \ | |
if (_fsm##EnumName##Step == FSM::Enter) \ | |
Update##EnumName(); \ | |
} \ | |
EnumName Get##EnumName() const { return _fsm##EnumName; } \ | |
bool Is##EnumName(EnumName state, FSM step=FSM::Max) const \ | |
{ \ | |
return _fsm##EnumName == state && \ | |
(step == FSM::Max || _fsm##EnumName##Step == step); \ | |
} \ | |
#define FSM_IMPL_BEGIN(ClassName, EnumName) \ | |
void ClassName::Update##EnumName() \ | |
{ \ | |
auto step = _fsm##EnumName##Step; \ | |
auto next = &ClassName::Next##EnumName; \ | |
if (step == FSM::Enter || step == FSM::Leave) \ | |
Next##EnumName(); \ | |
switch (_fsm##EnumName) \ | |
{ \ | |
default: \ | |
{ \ | |
if (step == FSM::Enter || step == FSM::Leave) \ | |
(this->*next)(); \ | |
#define FSM_STATE(State) \ | |
} \ | |
break; \ | |
} \ | |
case State: \ | |
{ \ | |
switch (step) \ | |
{ \ | |
default: \ | |
if (step == FSM::Enter || step == FSM::Leave) \ | |
(this->*next)(); \ | |
#define FSM_DEFER true | |
#define __FSM_ON_2(Step, defer) \ | |
break; \ | |
case Step: \ | |
if ((step == FSM::Enter || step == FSM::Leave) && !defer) \ | |
(this->*next)(); \ | |
// Default macro argument handling from https://stackoverflow.com/a/27051616/ | |
#define __FSM_VARGS_(_10, _9, _8, _7, _6, _5, _4, _3, _2, _1, N, ...) N | |
#define __FSM_VARGS(...) __FSM_VARGS_(__VA_ARGS__, 10, 9, 8, 7, 6, 5, 4, 3, 2, 1, 0) | |
#define __FSM_CONCAT_(a, b) a##b | |
#define __FSM_CONCAT(a, b) __FSM_CONCAT_(a, b) | |
#define __FSM_ON_1(a) __FSM_ON_2(a, false) | |
#define FSM_ON(...) __FSM_CONCAT(__FSM_ON_, __FSM_VARGS(__VA_ARGS__))(__VA_ARGS__) | |
// Default handler for noop state. Just switch forward. | |
#define FSM_IMPL_END() \ | |
} \ | |
break; \ | |
} \ | |
} \ | |
/* Sample: | |
enum SampleState | |
{ | |
On, | |
Off, | |
}; | |
struct FSMTest | |
{ | |
std::recursive_mutex lock; // Optional | |
FSM_DECLARE(SampleState); | |
}; | |
FSM_IMPL_BEGIN(FSMTest, SampleState) | |
{ | |
FSM_STATE(SampleState::On) | |
{ | |
FSM_ON(FSM::Enter) // Executed once on entry | |
{ | |
printf("On-Enter\n"); | |
} | |
FSM_ON(FSM::Update) // Executed continuously... | |
{ | |
printf("On-Update\n"); | |
SetSampleState(SampleState::Off); // ...until state change. | |
} | |
FSM_ON(FSM::Leave) // Executed once when changing to another state. | |
{ | |
printf("On-Leave\n"); | |
} | |
} | |
FSM_STATE(SampleState::Off) | |
{ | |
FSM_ON(FSM::Enter, FSM_DEFER) | |
{ | |
printf("Off-Enter\n"); | |
std::thread([this]() { | |
std::this_thread::sleep_for(std::chrono::seconds(2)); | |
printf("Off-Enter-switching\n"); | |
lock.lock(); | |
NextSampleState(); // Deferred completion. State will not switch to FSM::Update until this is called. | |
lock.unlock(); | |
}).detach(); | |
} | |
FSM_ON(FSM::Update) // FSM::Update can never be deferred. | |
{ | |
printf("Off-Update\n"); | |
SetSampleState(SampleState::On); // Switching to other state is allowed only from FSM::Update. | |
} | |
FSM_ON(FSM::Leave, FSM_DEFER) // Could be deferred or standard. | |
{ | |
printf("Off-Leave\n"); | |
std::thread([this]() { | |
std::this_thread::sleep_for(std::chrono::seconds(2)); | |
printf("Off-Leave-switching\n"); | |
lock.lock(); | |
NextSampleState(); | |
lock.unlock(); | |
}).detach(); | |
} | |
} | |
} | |
FSM_IMPL_END() | |
int main() | |
{ | |
FSMTest fsm; | |
fsm.SetSampleState(SampleState::Off); | |
for (;;) | |
{ | |
fsm.lock.lock(); | |
fsm.UpdateSampleState(); | |
fsm.lock.unlock(); | |
} | |
return 0; | |
} | |
*/ |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment