CXMessageTarget

From cxwiki

Revision as of 22:03, 15 April 2018 by Windwalkr (talk | contribs)

The CXMessageTarget baseclass allows a message-passing hierarchy to be built for the purpose of dispatching CXMessage event messages. The UIElement hierarchy derives from this class, however it is possible to use message-passing in other classes and scenarios.

Thread Safety

For the most part, CXMessageTargets are thread-agnostic but not reentrant. Some specific functionality is outright thread-safe, as noted below.

Message Handling

A CXMessage object is constructed (often on the stack) by the code which determines that the event has occurred. It is then passed to the virtual CXMessageTarget::Message() function on an appropriate CXMessageTarget (often "this"). If Message() is overriden, the messages are passed down the C++ class hierarchy until they reach the base CXMessageTarget::Message() at which point the message is passed to the parent CXMessageTarget and the process repeats. Messages may be handled by any overridden Message() function along the way. Handled messages may stop processing at that point, or may be allowed to propagate as normal, either before or after the handler code. In some cases, handler code may wish to absorb the message in question and re-emit a different message (actual modification of the original CXMessage object is not possible, although in rare cases the CXMessage object may include an immutable payload which in turn refers to a mutable object). Generally speaking, messages should be propagated by handlers unless there is a specific reason that the parent objects should never be aware of the message.

// Overridden to handle the specified message. Returns a result 
// code indicating the success of the handler. If the override
// is not interested in the message in question, it should pass
// it to the superclass implementation for further handling.
virtual CXResultCode Message(const CXMessage &msg);

Observation

Any CXMessageTarget object may "observe" other CXMessageTarget objects through the use of CXMessagePipes. Note that this does not automatically forward every message which passes through the observed object, but rather requires the observed object to explicitly broadcast the message using BroadcastToObservers().

The observer and observee objects do not have to belong to the same thread or same CXMessageTarget hierarchy, however care must be taken to avoid issues such as:

  • CXMessageTarget::Message() being called on an unexpected thread. This is typically avoided by NOT attaching an observer across threads, however in the event that observation across threads is required, the  recipient Message() function must be thread-safe including not calling the superclass Message() function in an unsafe manner.
  • Message-passing loops if Message() calls BroadcastToObservers(). 
  • Possible race conditions between observing a given object, and that object being deleted on another thread.
  • Deadlocking between BroadcastToObservers() on one thread and adding/removing an observer on another thread.

 

// "Broadcasts" the specified message to each observer of this   
// object, by calling their Message() function. Does nothing if
// there are currently no observers. Ignores any result codes
// returned by the Message() calls.
//
// Note: Thread-safe. Blocks against other observation-related 
// calls on other threads.
//
// Note: This function is safe against observers being added or
// removed from within the Message() calls, but makes no 
// guarantees as to whether the added/removed objects received
// the broadcast.
//
void BroadcastToObservers(const CXMessage &msg);


// Returns whether this object currently has any observers.
// This operation is thread-safe, but relying on the result
// introduces potential race conditions.
bool HasObservers(void) const;

// Returns the number of observers currently observing this
// object. The operation is thread-safe, but relying on the
// result introduces potential race conditions.
uint CountObservers(void) const;

Override Points

A number of functions are provided which can be overridden as required to manipulate the CXMessageTarget functionality.
// Returns the parent CXMessageTarget in this hierarchy. 
// Returns nullptr if this is the root of the hierarchy.
// Called from the base Message() implementation to determine
// whether the message can be passed up to a parent, but may
// also be called from other locations.
//
// Note: Not guaranteed thread-safe, which means that the
// base Message() implementation is also not guaranteed
// thread-safe, which in turns means that no Message()
// implementation is guaranteed thread-safe unless it
// explicitly avoids calling the base Message() 
// implementation, or unless the class in question
// redefines this function as thread-safe.
//
virtual CXMessageTarget* __nullable GetMessageParent(void);

// Returns a CXString which may help identify this object.
// This is a helper mechanism and is not used by the
// underlying message-passing implementation. The default
// implementation simply returns an empty string.
virtual CXString GetMessageTargetID(void) const;

// Returns a CXString 
virtual CXString AsDebugString(void) const;
  
// Called when adding the first observer to a previously
// non-observed target.
// Called on the thread which added the observer, which
// may not be the "owner" of the CXMessageTarget in
// question.
virtual void NotifyObserversAttached(void);
    
// Called when removing the last observer from a target.
// Called on the thread which removed the observer, 
// which may not be the "owner" of the CXMessageTarget
// in question.
virtual void NotifyObserversDetached(void);