yaze 0.2.0
Link to the Past ROM Editor
 
Loading...
Searching...
No Matches
Message Passing

Overview

yaze includes a message passing and notification system as part of its core library. Supports message filtering, dynamic method binding, swizzling, and reflection. This message system was inspired by Objective-C and Cocoa's message passing system. It aims to overcome some of the difficulties with handling events in ImGui.

This system is currently in development and most of the content here was generated using ChatGPT to help me organize my thoughts. I will be updating this document as I continue to develop the system.

Key Components

  • Message: A basic unit of communication between components. It contains a type, sender, and an optional payload.
  • IMessageListener: An interface for objects that want to receive messages. Implement this interface to handle incoming messages.
  • MessageDispatcher: The central hub that registers listeners, dispatches messages, and manages protocols, filters, and dynamic handlers.
  • AsyncMessageDispatcher: An extension of MessageDispatcher that supports asynchronous message dispatching via a queue.
  • IMessageProtocol: An interface for defining custom protocols that can filter or handle messages based on specific criteria.
  • MessageFilter: A class used to filter which messages a listener should receive.
  • Swizzler: A utility that allows you to dynamically replace (swizzle) methods on objects at runtime, enabling dynamic behavior changes.
  • Reflectable: An interface that allows for runtime inspection and manipulation of objects' properties.
  • ObjectFactory: A factory for creating instances of Reflectable objects dynamically based on type names.

Getting Started

1. Setting Up the Message Dispatcher

To start using the message passing system, first create an instance of MessageDispatcher or AsyncMessageDispatcher depending on whether you need synchronous or asynchronous message handling.

The EditorManager in the main yaze app will have a MessageDispatcher instance which can be injected into the various Editor components. This will allow the components to communicate with each other without needing to know about each other.

2. Registering Listeners

Components that need to listen for messages must implement the IMessageListener interface and register themselves with the dispatcher.

class MyListener : public yaze::app::core::IMessageListener {
public:
void OnMessageReceived(const yaze::app::core::Message& message) override {
// Handle the message
}
};
MyListener listener;
dispatcher.RegisterListener("MyMessageType", &listener);
virtual absl::Status OnMessageReceived(const Message &message)=0
void RegisterListener(const std::string &message_type, IMessageListener *listener)
Definition message.cc:9

3. Sending Messages

To communicate between components, create a Message and send it through the dispatcher.

yaze::app::core::Message message("MyMessageType", this, some_payload);
dispatcher.SendMessage(message);
void SendMessage(const Message &message)
Definition message.cc:37

4. Using Protocols and Filters

For more advanced message handling, you can define custom protocols and filters. Protocols can determine if they can handle a message, while filters can refine which messages are received by a listener.

class MyProtocol : public yaze::app::core::IMessageProtocol {
public:
bool CanHandleMessage(const yaze::app::core::Message& message) const override {
// Define criteria for handling
return message.type == "MyMessageType";
}
};
dispatcher.RegisterProtocol(new MyProtocol());
class MyFilter : public yaze::app::core::MessageFilter {
public:
bool ShouldReceiveMessage(const yaze::app::core::Message& message) const override {
// Filter logic
return true; // Receive all messages of this type
}
};
dispatcher.RegisterFilteredListener("MyMessageType", &listener, std::make_unique<MyFilter>());
virtual bool CanHandleMessage(const Message &message) const =0
void RegisterProtocol(IMessageProtocol *protocol)
Definition message.cc:22
void RegisterFilteredListener(const std::string &message_type, IMessageListener *listener, std::unique_ptr< MessageFilter > filter)
Definition message.cc:26
virtual bool ShouldReceiveMessage(const Message &message) const =0

5. Dynamic Method Binding and Swizzling

To dynamically bind methods to message types or change the behavior of methods at runtime, use the Swizzler and BindHandler methods.

dispatcher.BindHandler("MyMessageType", [](const yaze::app::core::Message& message) {
// Handle the message dynamically
});
Swizzler swizzler;
swizzler.Swizzle(&some_object, &SomeClass::OriginalMethod, []() {
// New method behavior
});
void BindHandler(const std::string &message_type, MessageHandler handler)
Definition message.cc:32

6. Reflection and Object Creation

For systems that require dynamic inspection and manipulation of objects, implement the Reflectable interface and use the ObjectFactory to create instances dynamically.

class MyObject : public yaze::app::core::Reflectable {
public:
std::string GetTypeName() const override { return "MyObject"; }
std::vector<std::string> GetPropertyNames() const override { return {"property"}; }
std::any GetPropertyValue(const std::string& property_name) const override {
if (property_name == "property") return property_;
return {};
}
void SetPropertyValue(const std::string& property_name, const std::any& value) override {
if (property_name == "property") property_ = std::any_cast<int>(value);
}
private:
int property_;
};
factory.RegisterType<MyObject>("MyObject");
auto my_object = factory.CreateObject("MyObject");
std::unique_ptr< Reflectable > CreateObject(const std::string &type_name) const
Definition message.cc:120
void RegisterType(const std::string &type_name)
Definition message.cc:116
virtual void SetPropertyValue(const std::string &property_name, const std::any &value)=0
virtual std::vector< std::string > GetPropertyNames() const =0
virtual std::string GetTypeName() const =0
virtual std::any GetPropertyValue(const std::string &property_name) const =0