1
0
mirror of https://github.com/wqking/eventpp.git synced 2024-12-27 16:41:11 +08:00
eventpp/doc/argumentadapter.md

9.9 KiB

Argument adapter reference

Description

The header file eventpp/utilities/argumentadapter.h contains utilities that can cast pass-in argument types to the types of the functioning being called. It's as if the argument types are casted using static_cast for most types or static_pointer_cast for shared pointers.

For example,

struct Event
{
};

struct MouseEvent : public Event
{
};

eventpp::EventDispatcher<int, void (const Event &)> dispatcher;

// Below line won't compile because the listener parameter `const Event &` can't be converted to `const MouseEvent &` explicitly.
//dispatcher.appendListener(ON_MOUSE_DOWN, [](const MouseEvent &) {});

// This line works.
dispatcher.appendListener(ON_MOUSE_DOWN, eventpp::argumentAdapter<void(const MouseEvent &)>([](const MouseEvent &) {}));

Note: above code may not work with EventQueue, because EventQueue stores the arguments in memory, here is Event. Passing MouseEvent will be stored as Event. std::shared_ptr should be used in EventQueue. See the sample code example3 in this document.

Header

eventpp/utilities/argumentadapter.h

API reference

template <template parameters>
ArgumentAdapter<template parameters> argumentAdapter(Func func);

Function argumentAdapter receives a function func, and return a functor object of ArgumentAdapter. ArgumentAdapter has a function invoking operator that can cast the arguments to match the parameter types of func. The return value of argumentAdapter can be passed to CallbackList, EventDispatcher, or EventQueue.
If func is a std::function, or a pointer to free function, argumentAdapter can deduce the parameter types of func, then argumentAdapter can be called without any template parameter.
If func is a functor object that argumentAdapter can't deduce the parameter types, argumentAdapter needs a template parameter which is the prototype of func.
ArgumentAdapter converts argument types using static_cast. For std::shared_ptr, std::static_pointer_cast is used. If static_cast or std::static_pointer_cast can't convert the types, compile errors are issued.
Caveat: Sucessful type casting doesn't mean correct. For example (pseudo code),

class A;
class B : public A;
class C : public A;
eventpp::EventDispatcher<int, void (const A &)> eventDispatcher;
eventDispatcher.appendListener(3, eventpp::argumentAdapter<void(const B &)>([](const B & e) {}));
eventDispatcher.dispatch(3, C());

The code can compile successfully, but the listener will receive a C object which is wrongly casted to B, that's a serious problem that most likely will crash the program. To avoid such mistake, be very careful that the correct types are passed to the corresponding listener. It's usually safe that in an event system, each event type has its own event class, such as a mouseDown event has MouseDown event class.
Or you can use conditionalFunctor to check in advance if the event type matches the desired class. The example2 in below example code shows how to use conditionalFunctor.

Below is the example code to demonstrate how to use argumentAdapter. There are full compile-able example code in file 'tests/tutorial/tutorial_argumentadapter.cpp '.

// In the tutorials here, we define an event class hierarchy, Event is the base class.
// The callback prototype in EventDispatcher is reference or pointer to Event,
// then we should only be able to add listeners that only accept reference or pointer to Event,
// not derived class such as MouseEvent.
// But with argumentAdapter, the listeners can accept reference or pointer to MouseEvent,
// and argumentAdapter converts any reference or pointer to Event to MouseEvent automatically, as
// long as object pointed to the reference or pointer is a MouseEvent.

// Define the event types
enum class EventType
{
	// for MouseEvent
	mouse,

	// for KeyEvent
	key,

	// for MessageEvent
	message,

	// For either MouseEvent or KeyEvent, we use this type to demonstrate
	// how to use conditionalFunctor
	input,
};

class Event
{
public:
	Event() {
	}

	// Make the Event polymorphism so we can use dynamic_cast to detect
	// if it's a MouseEvent or KeyEvent
	virtual ~Event() {
	}

};

class MouseEvent : public Event
{
public:
	MouseEvent(const int x, const int y)
		: x(x), y(y)
	{
	}

	int getX() const { return x; }
	int getY() const { return y; }

private:
	int x;
	int y;
};

class KeyEvent : public Event
{
public:
	explicit KeyEvent(const int key)
		: key(key)
	{
	}

	int getKey() const { return key; }

private:
	int key;
};

class MessageEvent : public Event
{
public:
	explicit MessageEvent(const std::string & message)
		: message(message) {
	}

	std::string getMessage() const { return message; }

private:
	std::string message;
};

// A free function that will be added as listener later.
// argumentAdapter works on all types of callables, include but not limited to,
// lambda, functor object, std::function, free function, etc.
void tutorialArgumentAdapterFreeFunction(const MouseEvent & e)
{
	std::cout << "Received MouseEvent in free function, x=" << e.getX() << " y=" << e.getY() << std::endl;
}

void example1()
{
	eventpp::EventDispatcher<EventType, void (const Event &)> eventDispatcher;

	// callback 1 -- lambda, or any functor object

	// This can't compile because a 'const Event &' can be passed to 'const MouseEvent &'
	//eventDispatcher.appendListener(mouseEventId, [](const MouseEvent & e) {});

	// This compiles. eventpp::argumentAdapter creates a functor object that static_cast 
	// 'const Event &' to 'const MouseEvent &' automatically.
	// Note we need to pass the function type to eventpp::argumentAdapter because the lambda
	// doesn't have any function type information and eventpp::argumentAdapter can't deduce
	// the type. This rule also applies to other functor object.
	eventDispatcher.appendListener(
		EventType::mouse,
		eventpp::argumentAdapter<void(const MouseEvent &)>([](const MouseEvent & e) {
			std::cout << "Received MouseEvent in lambda, x=" << e.getX() << " y=" << e.getY() << std::endl;
		})
	);
	eventDispatcher.appendListener(
		EventType::message,
		eventpp::argumentAdapter<void(const MessageEvent &)>([](const MessageEvent & e) {
			std::cout << "Received MessageEvent in lambda, message=" << e.getMessage() << std::endl;
		})
	);

	// callback 2 -- std::function
	// We don't need to pass the function type to eventpp::argumentAdapter because it can
	// deduce the type from the std::function
	eventDispatcher.appendListener(
		EventType::key,
		eventpp::argumentAdapter(std::function<void(const KeyEvent &)>([](const KeyEvent & e) {
			std::cout << "Received KeyEvent in std::function, key=" << e.getKey() << std::endl;
		}))
	);

	// callback 3 -- free function
	// We don't need to pass the function type to eventpp::argumentAdapter because it can
	// deduce the type from the free function
	eventDispatcher.appendListener(
		EventType::mouse,
		eventpp::argumentAdapter(tutorialArgumentAdapterFreeFunction)
	);

	eventDispatcher.dispatch(EventType::mouse, MouseEvent(3, 5));
	eventDispatcher.dispatch(EventType::key, KeyEvent(255));
	eventDispatcher.dispatch(EventType::message, MessageEvent("Hello, argumentAdapter"));
	// In syntax we can dispatch KeyEvent under EventType::mouse, in our case,
	// the EventType::mouse listener casts KeyEvent to MouseEvent, which is invalid object,
	// and the listener will either use garbled data, or crash.
	//eventDispatcher.dispatch(EventType::mouse, KeyEvent(255));
}

void example2()
{
eventpp::EventDispatcher<EventType, void(const Event &)> eventDispatcher;

// Here we add two listener of MouseEvent and KeyEvent under the same event type 'input'.
// We use eventpp::conditionalFunctor to determine whether the Event matches the expected
// event type.

// listener 1
eventDispatcher.appendListener(
	EventType::input,
	eventpp::conditionalFunctor(
		eventpp::argumentAdapter<void(const MouseEvent &)>([](const MouseEvent & e) {
			std::cout << "Received MouseEvent in conditional tutorial, x=" << e.getX() << " y=" << e.getY() << std::endl;
		}),
		// This lambda is the condition. We use dynamic_cast to check if the event is desired.
		// This is for demonstration purpose, in production you may use a better way than dynamic_cast.
		[](const Event & e) { return dynamic_cast<const MouseEvent *>(&e) != nullptr; }
	)
);
// listener 2
eventDispatcher.appendListener(
	EventType::input,
	eventpp::conditionalFunctor(
		eventpp::argumentAdapter<void(const KeyEvent &)>([](const KeyEvent & e) {
			std::cout << "Received KeyEvent in conditional tutorial, key=" << e.getKey() << std::endl;
		}),
		[](const Event & e) { return dynamic_cast<const KeyEvent *>(&e) != nullptr; }
	)
);

// listener 1 will receive this event, listener 2 will not.
eventDispatcher.dispatch(EventType::input, MouseEvent(3, 8));

// listener 2 will receive this event, listener 1 will not.
eventDispatcher.dispatch(EventType::input, KeyEvent(99));
}

void example3()
{
// Here we use std::shared_ptr as the callback parameter.

// Note the argument can't be any reference to std::shared_ptr, such as 'const std::shared_ptr<Event> &',
// because eventpp::argumentAdapter uses std::static_pointer_cast to cast the pointer and it doesn't
// work on reference.
eventpp::EventQueue<EventType, void(std::shared_ptr<Event>)> eventQueue;

// This can't compile because a 'std::shared_ptr<Event>' can be passed to 'std::shared_ptr<MouseEvent>'
//eventDispatcher.appendListener(mouseEventId, [](std::shared_ptr<MouseEvent> e) {});

// This compiles. eventpp::argumentAdapter creates a functor object that static_cast 
// 'std::shared_ptr<Event>' to 'std::shared_ptr<MouseEvent>' automatically.
eventQueue.appendListener(
	EventType::mouse,
	eventpp::argumentAdapter<void(std::shared_ptr<MouseEvent>)>([](std::shared_ptr<MouseEvent> e) {
		std::cout << "Received MouseEvent as std::shared_ptr, x=" << e->getX() << " y=" << e->getY() << std::endl;
	})
);

eventQueue.enqueue(EventType::mouse, std::make_shared<MouseEvent>(3, 5));
eventQueue.process();
}