Related
I'm trying to find a way to share global variables of a specific Lua script(test.lua in the example) between different Lua states.
Here's my simple example code:
In test.lua
num = 2
In main.cpp
#include <iostream>
#include <lua.hpp>
int main()
{
lua_State *L1 = luaL_newstate(); //script A
luaL_openlibs(L1);
lua_settop(L1, 0);
luaL_dostring(L1, "require('test') num = 5");
lua_State *L2 = luaL_newstate(); //script B
luaL_openlibs(L2);
lua_settop(L2, 0);
luaL_dostring(L2, "require('test') print(num)");
lua_close(L1);
lua_close(L2);
}
I expect to get 5 but I get 2.
Is not possible to share global variables between different lua_State* through require?
ADDED :
If it's not possible, would it be a good idea to open test.lua using luaL_loadfile and then create getter/setter methods in C++ to share variable num between script A and B?
For example like this,
Script A:
script = my.Script("test")
script:setVar("num", 5)
Script B:
script = my.Script("test")
print(script:getVar("num"))
I wonder what you think about this design as an alternative to require.
Two distinct lua_States are completely and totally independent. One cannot directly affect anything that happens in another. You can expose some C code to one that allows it to modify the other, or they could both access some external resource (a file, for example) that allows them to share data.
But outside of things like this, no, they cannot interact.
The preferred method for this is to not make them separate lua_States.
Rather than having the global value in a Lua module, you could push a pointer to a C++ value as an upvalue for a metatable to a table which contains those globals. Then you push the globals table with the same metatable to both VMs. When you now access globals.num the getglobal and setglobal metamethods are triggered (depending on whether you read or write). These will update the value on the C++ side, such that it is shared between the two VMs.
N.B.: As you can judge from the lengthy boilerplate this is not a good solution. You should avoid having multiple VMs at the same time. If you require multiple VMs for concurrency purposes, consider using a mature library like Lua Lanes rather than rolling your own (doing this right requires several thousands of lines of code).
#include <string>
#include <lua.hpp>
int setglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = lua_tostring(L, 2);
luaL_argcheck(L, key == "num", 2, "unknown global");
int value = luaL_checkinteger(L, 3);
luaL_argcheck(L, lua_isnumber(L, 3), 3, "not a number");
int *num = static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
*num = value;
lua_pop(L, 1);
return 0;
}
int getglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = lua_tostring(L, 2);
luaL_argcheck(L, key == "num", 2, "unknown global");
int num = *static_cast<int *>(lua_touserdata(L, lua_upvalueindex(1)));
lua_pop(L, 1);
lua_pushinteger(L, num);
return 1;
}
static const struct luaL_Reg globals_meta[] = {
{"__newindex", setglobal},
{"__index", getglobal},
{nullptr, nullptr} // sentinel
};
int main() {
int num = 2;
// script A
lua_State *L1 = luaL_newstate();
luaL_openlibs(L1);
luaL_newmetatable(L1, "globals_meta");
lua_pushlightuserdata(L1, &num);
luaL_setfuncs(L1, globals_meta, 1);
lua_newuserdata(L1, 0);
luaL_getmetatable(L1, "globals_meta");
lua_setmetatable(L1, -2);
lua_setglobal(L1, "globals");
luaL_dostring(L1, "print('Script A: ' .. globals.num) globals.num = 5");
// script B
lua_State *L2 = luaL_newstate();
luaL_openlibs(L2);
luaL_newmetatable(L2, "globals_meta");
lua_pushlightuserdata(L2, &num);
luaL_setfuncs(L2, globals_meta, 1);
lua_newuserdata(L2, 0);
luaL_getmetatable(L2, "globals_meta");
lua_setmetatable(L2, -2);
lua_setglobal(L2, "globals");
luaL_dostring(L2, "print('Script B: ' .. globals.num)");
lua_close(L1);
lua_close(L2);
}
As a challange to myself I implemented a complete global table which can communicate values of type nil, bool, int, double, and string between two Lua states. They can be named with everything that has a string representation.
-- To be on the safe side, just use numbers and strings as keys
globals[1] = "x"
globals.num = 5
-- Be careful when using table or function literals as keys
-- Two empty tables don't have the same representation
globals[{}] = 2 -- "table: 0x10d55a0" = 2
globals[{}] = 1 -- "table: 0x10ce2c0" = 1
I haven't checked all sorts of exceptional situations exhaustively, so no refunds!
#include <iostream>
#include <string>
#include <unordered_map>
#include <boost/variant.hpp>
#include <lua.hpp>
enum class nil {};
using Variant = boost::variant<nil, bool, int, double, std::string>;
int setglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = luaL_tolstring(L, 2, nullptr);
auto &globals = *static_cast<std::unordered_map<std::string, Variant> *>(
lua_touserdata(L, lua_upvalueindex(1)));
Variant &v = globals[key];
switch (lua_type(L, 3)) {
case LUA_TNIL:
v = nil{};
break;
case LUA_TBOOLEAN:
v = static_cast<bool>(lua_toboolean(L, 3));
lua_pop(L, 1);
break;
case LUA_TNUMBER:
if (lua_isinteger(L, 3)) {
v = static_cast<int>(luaL_checkinteger(L, 3));
} else {
v = static_cast<double>(luaL_checknumber(L, 3));
}
lua_pop(L, 1);
break;
case LUA_TSTRING:
v = std::string(lua_tostring(L, 3));
lua_pop(L, 1);
break;
default:
std::string error = "Unsupported global type: ";
error.append(lua_typename(L, lua_type(L, 3)));
lua_pushstring(L, error.c_str());
lua_error(L);
break;
}
return 0;
}
int getglobal(lua_State *L) {
void *p = luaL_checkudata(L, 1, "globals_meta");
luaL_argcheck(L, p != nullptr, 1, "invalid userdata");
std::string key = luaL_tolstring(L, 2, nullptr);
auto globals = *static_cast<std::unordered_map<std::string, Variant> *>(
lua_touserdata(L, lua_upvalueindex(1)));
lua_pop(L, 1);
auto search = globals.find(key);
if (search == globals.end()) {
lua_pushstring(L, ("unknown global: " + key).c_str());
lua_error(L);
return 0;
}
Variant const &v = search->second;
switch (v.which()) {
case 0:
lua_pushnil(L);
break;
case 1:
lua_pushboolean(L, boost::get<bool>(v));
break;
case 2:
lua_pushinteger(L, boost::get<int>(v));
break;
case 3:
lua_pushnumber(L, boost::get<double>(v));
break;
case 4:
lua_pushstring(L, boost::get<std::string>(v).c_str());
break;
default: // Can't happen
std::abort();
break;
}
return 1;
}
static const struct luaL_Reg globals_meta[] = {
{"__newindex", setglobal},
{"__index", getglobal},
{nullptr, nullptr} // sentinel
};
int main() {
std::unordered_map<std::string, Variant> globals;
globals["num"] = 2;
// script A
lua_State *L1 = luaL_newstate();
luaL_openlibs(L1);
luaL_newmetatable(L1, "globals_meta");
lua_pushlightuserdata(L1, &globals);
luaL_setfuncs(L1, globals_meta, 1);
lua_newuserdata(L1, 0);
luaL_getmetatable(L1, "globals_meta");
lua_setmetatable(L1, -2);
lua_setglobal(L1, "globals");
if (luaL_dostring(L1, "print('Script A: ' .. globals.num)\n"
"globals.num = 5") != 0) {
std::cerr << "L1:" << lua_tostring(L1, -1) << '\n';
lua_pop(L1, 1);
}
// script B
lua_State *L2 = luaL_newstate();
luaL_openlibs(L2);
luaL_newmetatable(L2, "globals_meta");
lua_pushlightuserdata(L2, &globals);
luaL_setfuncs(L2, globals_meta, 1);
lua_newuserdata(L2, 0);
luaL_getmetatable(L2, "globals_meta");
lua_setmetatable(L2, -2);
lua_setglobal(L2, "globals");
if (luaL_dostring(L2, "print('Script B: ' .. globals.num)") != 0) {
std::cerr << "L1:" << lua_tostring(L2, -1) << '\n';
lua_pop(L2, 1);
}
lua_close(L1);
lua_close(L2);
}
While Lua states are separate by default, some binding libraries expose functionality to transfer information from one to the other.
For example, in sol, there are methods to serialize fairly arbitrary Lua data, including functions, to C++ data. You can then de-serialize that data into another Lua state, to effectively copy it (code link).
But you still will have two copies, in the end. You can't modify one Lua state from another directly.
Your last point, about exposing some getter/setter, is valid. You can have some data stored in C/C++ and have two different Lua states able to access it. You still have to bind that data to each VM separately.
Can someone explain to me why the following code for a unit test gives the error unreadVariable for n and k in cppcheck?
Combinations is a template class that calculates all combinations of n choose k but this should not matter here.
TEST(Combinations, ChooseOne)
{
const UINT8 n = 3;
const UINT8 k = 1;
Combinations<n, k> comb;
comb.calc();
std::vector< std::vector<UINT8> > _vui8Expect = { { 2 }, { 1 }, { 0 } };
EXPECT_THAT(comb.result, ::testing::ContainerEq(_vui8Expect));
}
I can change the code to the following and not get a cppcheck error anymore. But I do not like this, because it make the code less verbose. n, k are well defined quantities in statistics and they make it more clear in the call what is going on.
TEST(Combinations, ChooseOne)
{
Combinations<3, 1> comb;
comb.calc();
std::vector< std::vector<UINT8> > _vui8Expect = { { 2 }, { 1 }, { 0 } };
EXPECT_THAT(comb.result, ::testing::ContainerEq(_vui8Expect));
}
This is a known issue: http://trac.cppcheck.net/ticket/7542
So unless it will be fixed, the cppcheck will report this false positive.
I tried to put this in a comment, but here is a thought.
As far as I remember Google Tests is using TEST clause in a following manner:
TEST(test_case_name, test_name) {
... test body ...
}
I haven't personally encountered something similar, but in your case you have the very same name for the test case name, and the actual class you test.
To me it seems like some sort of name collision.
Have you tried renaming
TEST(Combinations, ChooseOne)
{
const UINT8 n = 3;
const UINT8 k = 1;
Combinations<n, k> comb;
comb.calc();
std::vector< std::vector<UINT8> > _vui8Expect = { { 2 }, { 1 }, { 0 } };
EXPECT_THAT(comb.result, ::testing::ContainerEq(_vui8Expect));
}
to a:
TEST(CombinationsTest, ChooseOne)
{
const UINT8 n = 3;
const UINT8 k = 1;
Combinations<n, k> comb;
comb.calc();
std::vector< std::vector<UINT8> > _vui8Expect = { { 2 }, { 1 }, { 0 } };
EXPECT_THAT(comb.result, ::testing::ContainerEq(_vui8Expect));
}
I've done quite some java coding already, but I'm totally new to c++ and have no idea what's going on with my code right now. This code gives me a compile error in the map standard library. It says: Cannot increment value of type ' std::_1::pari<int, int>', and is happening in map, insert(_InputIterator __f, _InputIterator __l) if that's of any relevance.
I know the stackoverflow community generally doesn't like solving other peoples homework, but I think I made a genuine attempt at implementing this and besides that I'm very curious as to what's going on.
typedef std::pair<int, int> location;
std::set<location> neighbours(location loc, std::set<std::pair<location, location>> labyrinth, int& size) {
std::set<location> neighbours;
location locFmin = location(loc.first - 1, loc.second);
location locSmin = location(loc.first, loc.second - 1);
location locFplus = location(loc.first + 1, loc.second);
location locSplus = location(loc.first, loc.second + 1);
if (loc.first - 1 >= 0 && labyrinth.find(std::pair<location, location>(loc, locFmin)) == labyrinth.end()) {
neighbours.insert(locFmin);
}
if (loc.second - 1 >= 0 && labyrinth.find(std::pair<location, location>(loc, locSmin)) == labyrinth.end()) {
neighbours.insert(locSmin);
}
if (loc.first + 1 < size && labyrinth.find(std::pair<location, location>(loc, locFplus)) == labyrinth.end()) {
neighbours.insert(locFplus);
}
if (loc.second + 1 < size && labyrinth.find(std::pair<location, location>(loc, locSplus)) == labyrinth.end()) {
neighbours.insert(locSplus);
}
return neighbours;
}
int Labyrinth(std::set<std::pair<location, location>> labyrinth, int size) {
std::map<location, location> forest;
std::set<location> level;
std::set<location> known;
known.insert(location(0,0));
level.insert(location(0,0));
while (!level.empty()) {
std::set<location> nextLevel;
for (location loc: level) {
for (location neighbour: neighbours(loc, labyrinth, size)) {
if (known.find(neighbour) != known.end()) {
known.insert(neighbour);
forest.insert(neighbour, loc);
nextLevel.insert(neighbour);
}
}
}
level = nextLevel;
}
std::list<location> path;
location walk = location(size - 1, size - 1);
path.push_front(walk);
while (walk != location(0, 0)) {
walk = forest[walk];
path.push_front(walk);
}
int answ = path.size();
return answ;
}
It's an algorithm that should perform a breadth-first search trough a square maze of size size with of course size * size location(x, y) objects.
the incoming list labyrinth defines the walls of the maze one can't go trough. Eventually the function should return the number of nodes contained in the shortest path from (0, 0) to (size - 1, size - 1).
this is a simple test for the algorithm
std::set<std::pair<location, location> > labyrinth;
labyrinth.insert(std::pair<location, location>(location(0, 0), location(1, 0)));
labyrinth.insert(std::pair<location, location>(location(0, 1), location(1, 1)));
labyrinth.insert(std::pair<location, location>(location(0, 2), location(0, 3)));
labyrinth.insert(std::pair<location, location>(location(1, 1), location(1, 2)));
labyrinth.insert(std::pair<location, location>(location(1, 2), location(2, 2)));
labyrinth.insert(std::pair<location, location>(location(2, 3), location(3, 3)));
labyrinth.insert(std::pair<location, location>(location(2, 2), location(3, 2)));
labyrinth.insert(std::pair<location, location>(location(2, 1), location(3, 1)));
int labAnswer = Labyrinth(labyrinth, 4);
std::cout << labAnswer << std::endl;
if (labAnswer == 13)
{
std::cout << "Correct" << std::endl;
}
else
{
std::cout << "Incorrect" << std::endl;
}
Before anyone starts to come up with better ideas to solve this problem. I got the idea for the bfs code from a graph bfs java implementation from a book on algorithms. I'm not interested in solving this puzzle more efficiently, there'll always be better ways to do something. I'd like to know what is going on with my code and possibly what c++ aspect I'm missing here.
You aren't using std::map correctly with insert,
the line
forest.insert(neighbour, loc);
should be
forest[neighbor] = loc;
Check your #include headers, you will need to
#include <map>
Also, if you add using namespace std; then you can skip adding std:: to every declaration.
I'm creating a C/C++ function which will be called from Lua. My function must call a library function who's signature is like this:
void libFunction( int val1, int val2, tSETTINGS * pSettings );
I'm given these C/C++ structs:
typedef struct
{
int cmd;
int arg;
} tCOMMAND;
typedef struct
{
int numberCommands;
int id;
tCOMMAND commands[1];
} tSETTINGS;
Maybe my thinking is all wrong on this, but from Lua I'm calling like this:
id = 42
val1 = 1
val2 = 2
cmd1 = { 3, 4 }
cmd2 = { 5, 6 }
commands = { cmd1, cmd2 }
settings = { #commands, id, commands }
mycfunction( val1, val2, settings )
I'm sure that I'm still not understanding the Lua stack as referenced from C++, since what I'm trying just doesn't work. My solution is able to retrieve val1, val2, #commands and id, but when I try to retrieve commands[0] and commands[1] I get {1, 2} and {2, 42} respectively.
My C++ is essentially like this (for this sample I'm discarding the values). I've already retrieved val1 and val2:
int stkNdx = 1;
lua_rawgeti(L, 3, stkNdx++ );
int numcmds = lua_tointeger(L, -1); // this successfully retrieves numberCommands 2
lua_pop(L, 1);
lua_rawgeti(L, 3, stkNdx++ );
int id = lua_tointeger(L, -1); // this successfully retrieves id 42
lua_pop(L, 1);
lua_pushvalue(L, -1 );
lua_pushnil(L);
int cmdNbr = 0;
for( lua_next(L, -2); cmdNbr < numcmds; cmdNbr++ )
{
lua_pushvalue(L, -2);
int cmd = lua_tointeger(L, -1);
int arg = lua_tointeger(L, -1);
lua_pop(L, 2);
lua_next(L, -2);
}
lua_pop(L, 1);
I've tried various permutations of lua_rawgeti() followed by lua_tonumber() and lua_pop(), with basically the same result.
This seems similar to this question, and my solution is modeled after that with no success.
Experimenting more I inserted this:
lua_pushnil(L);
while( lua_next(L, -2) )
{
if( ! lua_istable(L, -1) )
{
int v = lua_tointeger(L, -1);
}
lua_pop(L, 1);
}
This loop executes 4 times. The first 2 times the values 2 and 42 are assigned to v. The next 2 iterations skip the assignment (lua_istable returned true). So it seems that although I've already retrieved numcmds and id, they're still there on the stack. I also clearly don't understand how to iterate over the subtables when they're encountered.
Lua table indices range from [1 .. N] instead of [0 .. N-1].
Your loop should be:
int cmdNbr = 1;
for( lua_next(L, -2); cmdNbr <= numcmds; cmdNbr++ )
{
...
}
or as I prefer it:
lua_rawgeti(L, 3, 2 );
int id = lua_tointeger(L, -1); // this successfully retrieves id 42
lua_pop(L, 1);
lua_rawgeti(L, 3, 3);
{
// commands table at stack top
size_t N = lua_objlen(L,-1); // size of the table
for (int i = 1; i <= N; ++i)
{
lua_rawgeti(L,-1, i); // cmd# at stack top
{
lua_rawgeti(L,-1,1); // first entry
int cmd = lua_tointeger(L,-1);
lua_pop(L,1);
lua_rawgeti(L,-1,2); // second entry
int arg = lua_tointeger(L,-1);
lua_pop(L,1);
}
lua_pop(L, 1); // pop cmd#
}
}
lua_pop(L, 1); // pop commands table
Note that, with the function lua_objlen(L,idx), it's not necessary to pass numcmds.
I have a state machine as described below.
We can start in one of two starting states, but we must hit all 4 states of the handshake. From there, we can either transfer a payload of data or receive a payload of data. Then, we return to our original starting state.
Handshake:
-> StartingState1 -> FinalState1 -> StartingState2 -> FinalState2
-> StartingState2 -> FinalState2 -> StartingState1 -> FinalState1
Payload Transfer:
-> SendPayload -> SendEnd -> StartingState?
-> ReceivePayload -> ReceiveEnd -> StartingState?
The code below represents my current architecture. Unfortunately, at the end of each process, I don't have enough information from within the states to know what the next state is I should hit.
Does anybody have any suggestions on how to improve this architecture based on my requirements?
Thanks,
PaulH
class MyMachine;
class Payload;
class IState
{
MyMachine* context_;
IState( MyMachine* context ) : context_( context) {};
virtual void Consume( byte data );
void ChangeState( IState* state )
{
context_->SetState( state );
}
}
class FinalState1 : IState
{
void Consume( byte data )
{
// Either go to StartingState1, SendPayload, or ReceivePayload.
// How can I tell from within the context of this state where I
// should go?
}
}
class StartingState1 : IState
{
void Consume( byte data )
{
if ( /*some condition*/ )
{
ChangeState( new FinalState1( context_ ) );
}
}
}
class MyMachine
{
IState* state_;
Payload* payload_;
void Start1( Mode mode )
{
state_ = new StartingState1( this );
}
void Start2( Mode mode )
{
state_ = new StartingState2( this );
}
void Consume( byte data )
{
state_->Consume( data );
}
void SetPayload( const Payload* payload )
{
payload_ = payload;
}
const Payload* GetPayload()
{
return payload_;
}
void SetState( State* state )
{
delete state_;
state_ = state;
}
}
// get a byte of data from some source
byte GetData();
void main()
{
MyMachine machine;
Payload payload;
machine.SetPayload( payload );
machine.Start1( Mode::SendPayload );
// could also call:
// machine.Start1( Mode::ReceivePayload );
// machine.Start2( Mode::SendPayload );
// machine.Start2( Mode::ReceivePayload );
for(;;)
{
machine.Consume( GetData() );
}
}
What you have doesn't represent the possible states of your system completely, but it's easy to transform it so that it does. You need additional states to represent the difference between being in state 1 and not having been in state 2, and being in state 1, whilst having been in state 2 (and the same for state 2). So you need:
S1 S2 F1 F2 S12 F12 S21 F21
SP SE
RP RE
with transitions
S1 --> F1
F1 --> S12
S12 --> F12
F12 --> SP or F12 --> RP
S2 --> F2
F2 --> S21
S21 --> F21
F21 --> SP or F21 --> RP
SP --> SE
RP --> RE
SE --> S1 or SE --> S2
RE --> S1 or RE --> S2
The key difference is the introduction of new states S12, F12, S21 and F21. In terms of implementation you could almost certainly just derive S12 from S2, F12 from F2, S21 from S1 and F21 from F2 and override the transition function to go to the correct state.
(Apologies for acronymising all your states).
Did you look at boost::statechart library?
I suggest designing from the point of view of function object or function pointers.
A simple state machine can be implemented using an array or std::map. Use the current state as an index and retrieve either the new state or a pointer to the state function.
More complex state machines travel from one state to another based on a transition or event. Simply implemented, this requires a 'nested' array. A container of transition containers. The first access gives you the transition table for a state. Use the current transition as an index into the transition table to return a function pointer of the function that handles this transition.
There different data structures that can be used, all depending on the complexity of your state machine.
A nice idea is to have a table driven state machine. This allows the engine to be coded and tested once. Changing the state machine involves changing the data in the table. The table may be able to exist outside of the executable, which means that the executable doesn't have to change. This concept can be expanded by using dynamic libraries, reducing the need to change the executable.
This is just my suggestion, I could be wrong (paraphrased from Dennis Miller).
Here is an example using the method suggested by Thomas:
#include <cassert>
#include <iostream>
#include <map>
class Machine;
typedef void (*StateFunctionPtr)(Machine& context);
// State "do" functions
void starting1(Machine& context) {std::cout << "S1 ";}
void final1(Machine& context) {std::cout << "F1 ";}
void starting2(Machine& context) {std::cout << "S2 ";}
void final2(Machine& context) {std::cout << "F2 ";}
void sendPayload(Machine& context) {std::cout << "SP ";}
void sendEnd(Machine& context) {std::cout << "SE ";}
void receivePayload(Machine& context) {std::cout << "RP ";}
void receiveEnd(Machine& context) {std::cout << "RE ";}
namespace State
{
enum Type {start, handshake1, handshake2, handshake3,
handshake4, xferPayload, endPayload};
};
// Aggregate of state, "mode" variables, and events.
struct StateKey
{
// Needed for use as map key
bool operator<(const StateKey& rhs) const
{
return
(state < rhs.state)
|| ( (state == rhs.state) && (isReceiving < rhs.isReceiving) )
|| ( (state == rhs.state) && (isReceiving == rhs.isReceiving)
&& (startsAt2 < rhs.startsAt2) );
}
bool startsAt2;
bool isReceiving;
State::Type state;
};
struct StateEffect
{
StateFunctionPtr function; // "do" function
State::Type newState; // state to transition to
};
struct StatePair
{
StateKey key;
StateEffect effect;
};
const StatePair stateTable[] =
{
{{0, 0, State::start}, {&starting1, State::handshake1}},
{{0, 0, State::handshake1}, {&final1, State::handshake2}},
{{0, 0, State::handshake2}, {&starting2, State::handshake3}},
{{0, 0, State::handshake3}, {&final2, State::handshake4}},
{{0, 0, State::handshake4}, {&sendPayload, State::xferPayload}},
{{0, 0, State::xferPayload}, {&sendEnd, State::endPayload}},
{{0, 0, State::endPayload}, {&starting1, State::handshake1}},
{{0, 1, State::start}, {&starting1, State::handshake1}},
{{0, 1, State::handshake1}, {&final1, State::handshake2}},
{{0, 1, State::handshake2}, {&starting2, State::handshake3}},
{{0, 1, State::handshake3}, {&final2, State::handshake4}},
{{0, 1, State::handshake4}, {&receivePayload, State::xferPayload}},
{{0, 1, State::xferPayload}, {&receiveEnd, State::endPayload}},
{{0, 1, State::endPayload}, {&starting1, State::handshake1}},
{{1, 0, State::start}, {&starting2, State::handshake1}},
{{1, 0, State::handshake1}, {&final2, State::handshake2}},
{{1, 0, State::handshake2}, {&starting1, State::handshake3}},
{{1, 0, State::handshake3}, {&final1, State::handshake4}},
{{1, 0, State::handshake4}, {&sendPayload, State::xferPayload}},
{{1, 0, State::xferPayload}, {&sendEnd, State::endPayload}},
{{1, 0, State::endPayload}, {&starting2, State::handshake1}},
{{1, 1, State::start}, {&starting2, State::handshake1}},
{{1, 1, State::handshake1}, {&final2, State::handshake2}},
{{1, 1, State::handshake2}, {&starting1, State::handshake3}},
{{1, 1, State::handshake3}, {&final1, State::handshake4}},
{{1, 1, State::handshake4}, {&receivePayload, State::xferPayload}},
{{1, 1, State::xferPayload}, {&receiveEnd, State::endPayload}},
{{1, 1, State::endPayload}, {&starting2, State::handshake1}}
};
class Machine
{
public:
Machine()
{
// Initialize state chart map from constant state table
const size_t tableSize = sizeof(stateTable) / sizeof(stateTable[0]);
for (size_t row=0; row<tableSize; ++row)
{
stateChart_[stateTable[row].key] = stateTable[row].effect;
}
}
// If startsAt2==true, then FSM will start with starting2 handshake function
void reset(bool startsAt2, bool isReceiving)
{
stateKey_.startsAt2 = startsAt2;
stateKey_.isReceiving = isReceiving;
stateKey_.state = State::start;
}
void step()
{
StateChart::const_iterator iter = stateChart_.find(stateKey_);
assert(iter != stateChart_.end());
const StateEffect& effect = iter->second;
effect.function(*this);
stateKey_.state = effect.newState;
}
private:
typedef std::map<StateKey, StateEffect> StateChart;
StateChart stateChart_;
StateKey stateKey_;
};
int main()
{
Machine machine;
machine.reset(true, false);
for (int i=0; i<20; ++i)
{
machine.step();
}
}
It compiles and works on my machine. You might want to add the following features:
Entry/exit functions in StateEffect
Event "triggers" in StateKey
Generalize into a template.
Add enough generic features to it, and it will start resembling a Boost.StateChart wannabe. ;-)
You can model your state machine using a Petri net. This allows you to define both very simple and very complex state machines.
To implement the state machine/ petri net you specified you can use an engine like the PTN Engine.
It allows you to declaratively define the whole state machine in the Petri net constructor. You can integrate your own functions to be called when reaching a given state, as well as functions to trigger state changes.