Sample code

Event and delegate handler

This sample shows the implementation of a simple yet powerful event system in C++11.

The objective of the event class is to implement a single event for which handlers can be added with the simple "event += handler" syntax inspired from C#.

Each handler function is encapsulated within a std::function object. Handlers can be formed out of a class method and an instance object using the make_handler() helper function. The resulting std::function handler is based on a lambda closure in order to retain a reference to the object instance.

Firing the event is simple, similar to a function call, thanks to the overloading of operator().

Thanks to the use of variadic templates, an event can take any number of parameters and return any kind of value.

The event class implements perfect forwarding, allowing to use rvalue references (temporary objects) as event arguments.

The code is complemented with an example application showing the basic usage of the event system.

Top

#include <iostream>
#include <vector>
#include <functional>

/**
 * @brief Event class.
 * @tparam F Handler function signature.
 */
template <typename F>
class event
{
public:
	typedef std::function<F> Handler;
	
	/**
	 * @brief Add a new handler to this event.
	 */
	event<F>& operator+=(Handler&& handler)
	{
		if ( handler )
			handlers_.push_back(std::forward<Handler>(handler));
		return (*this);
	}
	
	/**
	 * @brief Fire the event.
	 */
	template <typename... Args>
	void fire(Args&&... args) const
	{
		for ( Handler f : handlers_ )
			f(std::forward<Args>(args)...);
	}
	
	/**
	 * @brief Fire the event, and get the return values from all the handlers.
	 */
	template <typename R, typename... Args>
	std::vector<R> fireRet(Args&&... args) const
	{
		std::vector<R> ret;
		for ( Handler f : handlers_ )
			ret.push_back(f(std::forward<Args>(args)...));
		return ret;
	}
	
	/**
	 * @brief Fire the event.
	 */
	template <typename... Args>
	void operator()(Args&&... args) const
	{
		for ( Handler f : handlers_ )
			f(std::forward<Args>(args)...);
	}
	
protected:
	
	std::vector<Handler> handlers_; // Collection of event handlers.
};

/**
 * @brief Make an event handler from an object instance and a method pointer.
 * @tparam O    Object type.
 * @tparam R    Return value type.
 * @tparam Args Types of arguments.
 */
template <class O, typename R, typename... Args>
typename event<R(Args...)>::Handler make_handler(O& obj, R (O::* method)(Args...))
{
	return [=,&obj](Args&&... args) { return (obj.*method)(std::forward<Args>(args)...); };
}

/**
 * @brief Make an event handler from an object instance and a method pointer.
 * @tparam O    Object type.
 * @tparam R    Return value type.
 * @tparam Args Types of arguments.
 */
template <class O, typename R, typename... Args>
typename event<R(Args...)>::Handler make_handler(const O& obj, R (O::* method)(Args...) const)
{
	return [=,&obj](Args&&... args) { return (obj.*method)(std::forward<Args>(args)...); };
}

/**
 * @brief Simple class to encapsulate a single value and test perfect forwarding.
 */
class ClassVal
{
public:
	int i_;
	ClassVal(int i) : i_(i) { std::cout << "ClassVal::ClassVal(" << i_ << ") : this=" << (void*)this << "\n"; }
	ClassVal(const ClassVal& other) : i_(other.i_) { std::cout << "ClassVal::copy(" << i_ << ")\n"; }
	ClassVal(ClassVal&& other) : i_(other.i_) { other.i_ = -1; std::cout << "ClassVal::move(" << i_ << ")\n"; }
	ClassVal& operator=(const ClassVal& other) { i_ = other.i_; std::cout << "ClassVal::copy=(" << i_ << ")\n"; }
	ClassVal& operator=(ClassVal&& other) { i_ = other.i_; other.i_ = -1; std::cout << "ClassVal::move=(" << i_ << ")\n"; }
};

/**
 * @brief Dummy handler function.
 */
int func_a(double a1, ClassVal&& a2)
{
	std::cout << "func_a(" << a1 << ", " << a2.i_ << ") : &a2=" << (void*)&a2 << "\n";
}

/**
 * @brief Dummy class with handler methods.
 */
class ClassB
{
public:
	int func_b(double a1, ClassVal&& a2)
	{
		std::cout << "ClassB::func_b(" << a1 << ", " << a2.i_ << ") : &a2=" << (void*)&a2 << "\n";
	}
	int func_b_const(double a1, ClassVal&& a2) const
	{
		std::cout << "ClassB::func_b_const(" << a1 << ", " << a2.i_ << ") : &a2=" << (void*)&a2 << "\n";
	}
};

/**
 * @brief Dummy function returning a temporary to test perfect forwarding.
 */
ClassVal test_rval() { return ClassVal(99); }

/**
 * @brief Test application entry point.
 */
int main(int argc, char*argv[])
{
	event<void(double,ClassVal&&)> my_event;
	
	std::cout << "-- Initial without handler fire(0):\n";
	my_event(0, test_rval());
	std::cout << '\n';
	
	my_event += func_a;
	
	std::cout << "-- After normal function add fire(1):\n";
	my_event(1, test_rval());
	std::cout << '\n';
	
	ClassB b;
	my_event += make_handler(b, &ClassB::func_b);
	
	std::cout << "-- After method add fire(2):\n";
	my_event(2, test_rval());
	std::cout << '\n';
	
	my_event += make_handler(b, &ClassB::func_b_const);
	
	std::cout << "-- After method add (const) fire(3):\n";
	my_event(3, test_rval());
	std::cout << '\n';
}

Particle engine update method

Core update method for a simple CPU-based particle engine.

The method loops on all particles and update their energy based on the current time and the particle's time-to-live (TTL). Then, if the particle has some remaining energy and if the particle is not at rest (motion-less), the method updates the position by integrating Newton's equation of motion using the velocity verlet integration scheme.

In this simple example, a collision with the plane at Z = -1 is also presented in a hard-coded form. The collision is inelastic, taking into account some energy dissipation through a coefficient of restitution.

Top

/**
 * @brief Update all particles.
 * @param[in] dt   The time step since the last update, in seconds.
 * @param[in] fext The sum vector of the external forces applied to the particles.
 */
void ParticleEngine::update(float dt, const float3& fext)
{
	// Loop on particles
	for ( uint i = 0 ; i < particles_.size() ; ++i )
	{
		Particle& particle = particles_[i];
		
		// Update energy: E' = E - dE with dE = dt / TTL
		particle.energy_ -= dt * particle.invtimetolive_;
		if ( particle.energy_ <= 0.0f )
		{
			setInactive(particle);
			continue;
		}
		
		// Check motion simulation status
		if ( !particle.alive_ )
			continue;
		
		// Check motion, and update only if significant
		if ( particle.velocity_.length2() > minalivespeed2_ )
		{
			// Compute delta motion (verlet velocity integration)
			// x' = x + v.dt + 1/2.a.dt^2
			// a' = a(x')
			// v' = v + 1/2.(a + a').dt
			// Hypothesis: a = cst = Fext / m, so v' = v + a.dt = v + dv
			const float3 dv(fext * (dt * particle.invmass_));
			const float3 dx(particle.velocity_ * dt + dv * (0.5f * dt));
			
			// *** Example *********************************
			//
			// Collision with plane z = -1
			//
			// Collision with more objects and/or between the
			// particles thenselves is far more complex to
			// handle, and require at least a broad phase to
			// reduce the number of collision pairs to check
			// in the narrow phase.
			//
			static const float zplane = -1.0f;
			static const float cor    = 0.5f; // Coefficient of restitution
			if ( particle.position_.z + dx.z < zplane )
			{
				// We use continuous collision with TOI (time of impact)
				// This is not optimal for real-time simulation (slower)
				// but gives more accurate results and prevent tunnel
				// effect for fast moving objects.
				// Since in this example the plane is not moving and the,
				// simulation runs on the CPU, this is not so slow though.
				// Hypothesis: there is at most 1 collision per particle
				//             during the time frame 'dt'.
				
				// Compute delta time to TOI (time of impact)
				const float dt1 = (zplane - particle.position_.z)
						/ (particle.velocity_.z + 0.5f * dv.z);
				
				// Compute position and velocity at impact
				const float frac = clamp(dt1 / dt, 0.0f, 1.0f);
				particle.position_ += dx * frac;
				particle.velocity_ += dv * frac;
				
				// Collide, apply coefficient of restitution
				// NB: Here collision normal is +Z (plane normal)
				particle.velocity_.z = -particle.velocity_.z;
				particle.velocity_ *= cor;
				
				// Bounce
				if ( frac < 1.0f )
				{
					const float dt2 = dt - dt1;
					float3 dv2(fext * (dt2 * particle.invmass_));
					float3 dx2(particle.velocity_ * dt2 + dv2 * (0.5f * dt2));
					particle.position_ += dx2;
					particle.velocity_ += dv2;
				}
			}
			else
			{
				// Full step without collision
				particle.position_ += dx;
				particle.velocity_ += dv;
			}
			// *********************************************
		}
		else
		{
			// Particle too slow, put it to sleep
			particle.velocity_ = float3::zero;
			particle.alive_    = false;
		}
	}
}

TMX base64 decoder

Base64 decoder for parsing the tile identifiers of a stream of data in TMX file format.

The method decodes a stream of data encoded with the base64 encoding scheme. The resulting data, which constitutes the identifiers of the tiles of a layer from a TMX level, is then either directly used to populate an array of tile IDs, or passed down to a decompression method if the stream was compressed.

The method uses a lookup table to efficiently decode each base64-encoded block of data, then merges four 6-bit blocks into a set of three 8-bit tile identifiers.

If the stream is known to be compressed, the data is stored into a buffer whose size is computed from the initial base64-encoded stream size.

Top

/**
 * @brief Decode and decompress a base64-encoded stream of tile identifiers.
 * @param[in]  data        The base64-encoded and possibly compressed stream of data.
 * @param[in]  size        The size of the data stream @a data.
 * @param[in]  compression The compression method.
 * @param[out] tmxlayer    The layer to push tiles identifiers to after decoding and decompression.
 * @return Returns @c true on success, @c false on error.
 */
bool TMXLevelParser::decodeBase64Tiles(const uint8* data, const uint size,
	const Compression compression, TMXLayer& tmxlayer)
{
	assert(data && (size > 0u));
	
	// Allocate decode buffer for subsequent decompression
	uint  decodeLength;
	char* decodeBuffer;
	if ( compression != COMPRESSION_NONE )
	{
		// Align to 4: x -> ((x-1) + (x-1)%4 + 4) + 1
		decodeLength = size - 1u;
		decodeLength = ((decodeLength - (decodeLength & 0x3u) + 5u) >> 2u) * 3u;
		decodeBuffer = static_cast<char*>(malloc(decodeLength));
		if ( !decodeBuffer )
			return false;
	}
	
	// Loopkup table
	//   0xFF: invalid base 64 character
	//   0xC0: end-of-data '=' padding delimiter
	static const uint8 base64lookup[256] = {
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 62u,   0xFFu, 0xFFu, 0xFFu, 63u,
		52u,   53u,   54u,   55u,   56u,   57u,   58u,   59u,   60u,   61u,   0xFFu, 0xFFu, 0xFFu, 0xC0u, 0xFFu, 0xFFu,
		0xFFu, 0u,    1u,    2u,    3u,    4u,    5u,    6u,    7u,    8u,    9u,    10u,   11u,   12u,   13u,   14u,
		15u,   16u,   17u,   18u,   19u,   20u,   21u,   22u,   23u,   24u,   25u,   0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 26u,   27u,   28u,   29u,   30u,   31u,   32u,   33u,   34u,   35u,   36u,   37u,   38u,   39u,   40u,
		41u,   42u,   43u,   44u,   45u,   46u,   47u,   48u,   49u,   50u,   51u,   0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
		0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu, 0xFFu,
	};
	
	// Decode base64 blocks
	uint j = 0u;
	const uint8* const pend = data + size;
	for ( const uint8* pidx = data ; pidx < pend ; /* nothing */ )
	{
		// Get first 6-bit data
		uint8 b = base64lookup[*pidx];
		if ( b == 0xFFu ) // Invalid character
		{
			setError("Invalid character", pidx - data);
			return false;
		}
		if ( b == 0xC0u ) // Invalid end-of-data '=' padding delimiter
		{
			setError("Invalid end-of-data delimiter", pidx - data);
			return false;
		}
		uint32 block = (b << 18u);
		++pidx;
		if ( pidx >= pend )
		{
			setError("Premature end-of-stream", pidx - data);
			return false;
		}
		
		// Get second 6-bit data
		b = base64lookup[*pidx];
		if ( b == 0xFFu ) // Invalid character
		{
			setError("Invalid character", pidx - data);
			return false;
		}
		if ( b != 0xC0u ) // Valid end-of-data '=' padding delimiter
		{
			block |= (b << 12u);
			++pidx;
			if ( pidx >= pend )
			{
				setError("Premature end-of-stream", pidx - data);
				return false;
			}
			
			// Get third 6-bit data
			b = base64lookup[*pidx];
			if ( b == 0xFFu ) // Invalid character
			{
				setError("Invalid character", pidx - data);
				return false;
			}
			if ( b != 0xC0u ) // Valid end-of-data '=' padding delimiter
			{
				block |= (b << 6u);
				++pidx;
				if ( pidx >= pend )
				{
					setError("Premature end-of-stream", pidx - data);
					return false;
				}
				
				// Get fourth 6-bit data
				b = base64lookup[*pidx];
				if ( b == 0xFFu ) // Invalid character
				{
					setError("Invalid character", pidx - data);
					return false;
				}
				if ( b != 0xC0u ) // Valid end-of-data '=' padding delimiter
					block |= b;
				else
					pidx = pend;
				++pidx;
			}
			else
			{
				pidx = pend;
			}
		}
		else
		{
			pidx = pend;
		}
		
		// Check compression method
		if ( compression != COMPRESSION_NONE )
		{
			// Compressed: append data to buffer for later decompression
			decodeBuffer[j++] = (block & 0xFF0000u) >> 16u;
			decodeBuffer[j++] = (block & 0x00FF00u) >>  8u;
			decodeBuffer[j++] = (block & 0x0000FFu);
		}
		else
		{
			// Uncompressed: directly push tiles
			
			// Push first tile
			uint gid = (block & 0xFF0000u) >> 16u;
			tmxlayer.tiles.pushBack(gid);
			
			// Push second tile
			gid = (block & 0x00FF00u) >> 8u;
			tmxlayer.tiles.pushBack(gid);
			
			// Push last tile
			gid = (block & 0x0000FFu);
			tmxlayer.tiles.pushBack(gid);
		}
	}
	
	// If compressed, decompress data and push tiles
	if ( compression != COMPRESSION_NONE )
	{
		bool ret = decompressData(decodeBuffer, decodeLength, compression, tmxlayer);
		free(decodeBuffer);
		return ret;
	}
	
	// Return successfully
	return true;
}