Skip to content

Instantly share code, notes, and snippets.

@rokups
Last active November 25, 2018 09:49
Show Gist options
  • Save rokups/3cccf4b8830aad72f380a0b2c57e490f to your computer and use it in GitHub Desktop.
Save rokups/3cccf4b8830aad72f380a0b2c57e490f to your computer and use it in GitHub Desktop.
Tiny finite state machine implementation powered by preprocessor abuse.
//
// 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