Page MenuHomePhabricator

Globally accesible synchronization handler
Closed, ResolvedPublic

Description

We need a globally accessible synchronization handler, that

  • listens to the broadcasted synchronization events
  • is observable so that other view parts can process render window specific functions on those events

Event Timeline

kalali triaged this task as Normal priority.Feb 15 2018, 6:58 PM
kalali created this task.

Having a class that broadcasts the interaction events to anyone to listen, we need a synchronization handler class that reacts on the specific interaction events (zoom, move, scroll etc.).

The synchronization handler needs to:

  • know which render window wants to react on which event
  • know what action should be performed on such a render window for a specific event

In the classic observer pattern sense we would have a synchronization handler, that keeps a list of registered observers. A special function (e.g. Notify) calls a member function of all registered observers (e.g. observer[i]->update() or observer[i]->handleEvent(event)).

Since we know all events on which our synchronization handler class can react, we can implement different Notify-functions (e.g. NotifyOnZoomInteraction, NotifyOnMoveInteraction).
In order to update the correct observers, we can keep different lists of registered observers (e.g. ZoomObserverList, MoveObserverList).
The question still remains what function should be called. We don't want to add functions to the render windows (on which we will operate mostly when display interaction events are sent and received). So we might just store a list of std::functions that are called on an event.

Since we know all events on which our synchronization handler class can react, we can implement different Notify-functions (e.g. NotifyOnZoomInteraction, NotifyOnMoveInteraction).

I don't like this design! You basically implement against implementation here, instead doing it against interfaces. The observer should define the event he is interested in (like it is done in itk for example) be providing a reference event and not by being forced to choose a hard coded notification slot. This would meant that we need to change the handler interface whenever we want to implement a new event type.

Another solution would be to create a wrapper object (struct) that can be used to set as an observer for a certain event (the events coming from the synchronization broadcast class). On the event, the given std::function will be called.
The function has access to the event itself (as the event contains important information, e.g. the sender).

Such a wrapper object can be created multiple times with different std::functions to listen to different events. Introducing a new event allows to create a new wrapper object to observe the new event.

struct Observer
{
  typedef typename std::function<void(const itk::EventObject& eventObject)> CallbackType;
  CallbackType m_Callback;

  Observer(std::function<void(const itk::EventObject& eventObject)> callback) : m_Callback(callback) {}
  void Callback(const itk::EventObject& eventObject) { if (m_Callback) m_Callback(eventObject); }
};

To create such a wrapper object and connect the callback:

void ConnectDisplayInteractorZoomEvent(std::function<void(const itk::EventObject& eventObject)> callback)
{
  Observer* observer = new Observer(callback);

  typedef typename itk::ReceptorMemberCommand<Observer>::Pointer CommandPointer;
  CommandPointer command = itk::ReceptorMemberCommand<Observer>::New();
  command->SetCallbackFunction(observer, &Observer::Callback);
  m_Observable->AddObserver(DisplayInteractorZoomEvent(nullptr, 0, Point2D()), command);
}

private:
  itk::Object* m_Observable;

Later this can be used as:

mitk::Observer::CallbackType callback = [](const itk::EventObject& displayInteractorEvent) 
{ 
  const auto* zoomEvent = dynamic_cast<const mitk::DisplayInteractorZoomEvent*>(&displayInteractorEvent);
  assert(nullptr != zoomEvent);
/* code goes here */
};
ConnectDisplayInteractorZoomEvent(std::move(callback));

This task is not valid anymore:
Using the approach from the parent task (especially with the Singleton-variant), the Broadcast-class IS our globally accessible synchronization handler.
It receives concrete interaction events (e.g. move) and is observable, so that other instances can listen to its own events (e.g. DisplayInteractionMoveEvent).

The pasted code (wip) rather represents some kind of CustomDisplayInteractor (as opposed to the current DisplayInteractor). It only does not predefine functions that are called on an event (e.g. call Move on move resp on DisplayInteractionMoveEvent) but a custom std::function (which is wrapped inside the Observer-struct).
On this level, the CustomDisplayInteractor does not have to know anything about the concept of synchronization. It just reacts and processes its custom functions. The custom functions CAN BE used to achieve some kind of synchronization but this is not required.

This task will be closed now but remains as a look-up task for our decision process.