Challenge #2: Boost StateCharts

Hello Everyone,

this week is mostly about learning a new framework. And that framework is Boost StateCharts. A convenient API to implement a StateMachine. There is a simple example in the Boost Manual that creates a stop watch, and this example here is probably similar in complexity. But it is different example to flex the mind a little bit,  and learn to adapt the framework to new problems.

To get you started I have prepared some convenience and helper functions. The goal is to have a simple traffic lights simulation. I have also provided a trivial implementation, and some hints. Your task is it to implement the statechart based solution.

/* CPP Challenge 2:
 * Traffic Lights with Boost Statemachine
 */
#include <iostream>
#include <string>
#include <unistd.h>
#include <boost/statechart/event.hpp>
#include <boost/statechart/state_machine.hpp>
#include <boost/statechart/simple_state.hpp>
#include <boost/statechart/state.hpp>
#include <boost/statechart/transition.hpp>

std::string text_red(const std::string &input) 
{
	return "\033[1;31m" + input + "\033[0m";
}
std::string text_green(const std::string &input) 
{
	return "\033[1;32m" + input + "\033[0m";
}
std::string text_yellow(const std::string &input) 
{
	return "\033[1;33m" + input + "\033[0m";
}

struct Light
{
	Light() : isOn_(false) {}
	void on() { isOn_ = true; }
	void off() { isOn_ = false; }
	bool IsOn() const { return isOn_; }
	private:
		bool isOn_;
};

struct RedLight : Light { };
struct GreenLight : Light { };
struct YellowLight : Light { };

struct Lights
{
	RedLight red;
	GreenLight green;
	YellowLight yellow;
};

std::string showlight(const Light& light)
{
	if (light.IsOn()) {
		return "[*]";
	} else {
		return "[ ]";
	}
}

void show(const Lights& lights)
{
	std::cout << text_red(showlight(lights.red)) << "\n";
	std::cout << text_yellow(showlight(lights.yellow)) << "\n";
	std::cout << text_green(showlight(lights.green)) << "\n";
}



/*
 * Task:
 * Create a statemachine, "TrafficLights"
 * with two helper functions "Lights lights() const;"
 * and "int DelayTime() const", that returns a lights object,
 * and the time the machine needs to wait, before processing the next transition.
 *
 * 4 States: "Stop", "Prepare", "Go" and "Slow"
 *
 * Stop and Go should wait 5 Seconds, Prepare and Slow 1 Second each.
 */
int main(int argc, const char *argv[])
{
	Lights lights;

	lights.red.off();
	lights.yellow.off();
	lights.green.off();
	while (true) {
		lights.red.on();
		lights.yellow.off();
		show(lights); sleep(5);
		std::cout << std::endl;
		lights.yellow.on();
		show(lights); sleep(1);
		std::cout << std::endl;
		lights.red.off();
		lights.yellow.off();
		lights.green.on();
		show(lights); sleep(5);
		std::cout << std::endl;
		lights.green.off();
		lights.yellow.on();
		show(lights); sleep(1);
		std::cout << std::endl;
	}

	// This is how the statemachine based main loop should look:

	/*TrafficLight lights;
	lights.initiate();
	while(true) {
		lights.process_event(EvTransition());
		show(lights.lights());
		sleep(lights.DelayTime());
		std::cout << std::endl;
	}*/

	return 0;
}

So have fun with this problem. I already have a solution, and will post it next week. Probably I will protect the solution with a simple password to avoid spoilers. But you can get the password when you ask me via mail.

Cheers
-Richard

Solution Challenge #1: SimpleServer API

Hello Everyone,

so, I’ve created an implementation for the challenge from last week, and it is split into two parts, the header and the cpp file:

simpleserver.h:

#ifndef _SIMPLESERVER_H_
#define _SIMPLESERVER_H_

#include <string>
#include <queue>
#include <memory>
#include <boost/enable_shared_from_this.hpp>
#include <boost/asio/io_service.hpp>
#include <boost/asio/ip/tcp.hpp>

class IConnection
{
  // Copy from Challenge ...
};
class IConnectionFactory
{
  // Copy from Challenge ...
};
class SimpleServerConnection : public boost::enable_shared_from_this<SimpleServerConnection>
{
	public:
		typedef boost::shared_ptr<SimpleServerConnection> pointer;
		SimpleServerConnection(boost::asio::io_service& io_service);
		boost::asio::ip::tcp::socket& socket();
		void setConnection(std::shared_ptr<IConnection> connection);
		void start();
		void write(const std::string &value);
	private:
		void do_read();
		void do_write();
		boost::asio::ip::tcp::socket m_socket;
		enum { max_length = 1024 };
		char m_data[max_length];
		std::shared_ptr<IConnection> m_connection;
		std::queue<std::string> m_fifo;

};

class SimpleServer
{
public:
	SimpleServer(int portno, IConnectionFactory *connection_factory);
	~SimpleServer();
	void run();
private:
	void handle_accept(SimpleServerConnection::pointer new_connection,
			const boost::system::error_code& error);
	void start_accept();
	std::shared_ptr<IConnectionFactory> m_factory;
	boost::asio::io_service m_io_service;
	boost::asio::ip::tcp::acceptor m_acceptor;
};

#endif /* _SIMPLESERVER_H_ */

simpleserver.cpp

#include "simpleserver.h"
#include <boost/asio/placeholders.hpp>
#include <boost/asio/buffer.hpp>
#include <boost/asio/write.hpp>
#include <boost/asio/read.hpp>
#include <boost/bind.hpp>



void SimpleServerConnection::do_read()
{
	auto self(shared_from_this());
	m_socket.async_read_some(boost::asio::buffer(m_data, max_length),
			[this, self](boost::system::error_code ec, std::size_t length)
			{
				if (!ec) {
					std::string tmp(m_data, length);
					m_connection->receive(tmp);
					do_read();
				}
			});
}

void SimpleServerConnection::do_write()
{
	std::string value = m_fifo.front();
	m_fifo.pop();
	size_t length = std::min((size_t)value.length(), (size_t)max_length);
	memcpy(m_data, value.data(), length);
	auto self(shared_from_this());
	boost::asio::async_write(m_socket, boost::asio::buffer(m_data, length),
			[this, self](boost::system::error_code ec, std::size_t)
			{
				if (!m_fifo.empty()) {
					do_write();
				}
			});
}

void SimpleServerConnection::setConnection(std::shared_ptr<IConnection> connection)
{
	m_connection = connection;
}

void SimpleServerConnection::start()
{
	do_read();
}

void SimpleServerConnection::write(const std::string &value)
{
	m_fifo.push(value);
	if (m_fifo.size() == 1) {
		do_write();
	}
}



SimpleServerConnection::SimpleServerConnection(boost::asio::io_service& io_service) :
	m_socket(io_service)
{
}

boost::asio::ip::tcp::socket& SimpleServerConnection::socket()
{
    return m_socket;
}

SimpleServer::SimpleServer(int portno, IConnectionFactory *connection_factory) :
	m_factory(std::shared_ptr<IConnectionFactory>(connection_factory)),
	m_acceptor(m_io_service, boost::asio::ip::tcp::endpoint(boost::asio::ip::tcp::v4(), portno))
{
	start_accept();
}

SimpleServer::~SimpleServer()
{
}


void SimpleServer::handle_accept(SimpleServerConnection::pointer new_connection,
		const boost::system::error_code& error)
{
	if (!error) {
		std::shared_ptr<IConnection> c = std::shared_ptr<IConnection>(m_factory->create(
					boost::bind(&SimpleServerConnection::write, new_connection.get(), _1)));
		new_connection->setConnection(c);
		new_connection->start();
	}
	start_accept();
}

void SimpleServer::run()
{
	m_io_service.run();
}

void SimpleServer::start_accept()
{
	SimpleServerConnection::pointer new_connection = SimpleServerConnection::pointer(new SimpleServerConnection(m_io_service));
	m_acceptor.async_accept(new_connection->socket(),
			boost::bind(&SimpleServer::handle_accept, this, new_connection, boost::asio::placeholders::error));
}

Have fun,
-Richard

Challenge #1: SimpleServer API

This week I’ve prepared a little challenge in C++ programming. The example is not extrodinarily complex, but not completely simple either. The purpose of this example is, to challenge myself a little to get to know boost::asio networking, and to contemplate about how simple APIs for prototyping could look.

So the challenge is to create a class, that allows prototyping simple network servers, by implementing a convenient base class. The interfaces and base classes are already layed out, including an example that send the reply “Hello” for any incoming connection.

The specification is a bit open, and is not so much concerned with corner cases. So using the simplest solution possible is probably the right thing to do.

Ok, here we go for the boilerplate: read on, I will add a few explanations below.

#include <string>
#include <functional>
#include <iostream>

class IConnection
{
public:
    typedef std::function<void(const std::string& str)> send_function;
    virtual void receive(const std::string& str) = 0;
    virtual ~IConnection() {}
    IConnection(send_function send_) :
        send(send_) {}
protected:
    send_function send;
};

class IConnectionFactory
{
public:
    virtual IConnection* create(IConnection::send_function send_) = 0;
    virtual ~IConnectionFactory() {}
};

class HelloConnection : public IConnection
{
    public:
        HelloConnection(IConnection::send_function send_) :
            IConnection(send_) {}
        void receive(const std::string &str) {
            std::cout << "Received: " << str << std::endl;
            std::cout << "Sending Reply: \"Hello\"" << std::endl;
            send("Hello");
        }
};

class HelloConnectionFactory : public IConnectionFactory
{
    public:
        IConnection* create(IConnection::send_function send_) {
            return new HelloConnection(send_);
        }
};

class SimpleServer // <-- IMPLEMENT THIS CLASS!
{
    public:
        SimpleServer(int portno, IConnectionFactory *connection_factory) {}
        void run() {}
};

int main(int argc, const char *argv[])
{
    SimpleServer server(1234, new HelloConnectionFactory);
    server.run();
    return 0;
}

So, it goes like this: IConnection is the virtual interface for implementing a connection. That is, for every new connection, a new instance of an IConnection implementation is spawned, that is bound to that connection. When the connection is closed, the instance is deleted.

The IConnection receives a function pointer as argument, so that it can send replies. You can see how this is supposed to work in the example: “HelloConnection”. That example simple prints the received string on stdout, and sends “Hello” back.

SimpleServer is the class that you need to implement, here you can have all the bookkeeping, and you are probably going to need some internal connection class, that handles  each socket created, etc.

Anyways, I will probably be implementing the SimpleServer myself this week, so I might be able to post a follow up with a solution.

But anyways, this task is simple enough, that a veteran C++ programmer should be able to do it. And it is definitely intended as a practice to work one ones programming skill, so go ahead and try it.

Post your solution in the comments, or send them via email: richard.spindler@gmail.com

If you send via mail, I will add your solution to a follow-up post.:-)

Have fun,
Cheers
-Richard