/*********************************************************************************** * * Copyright (c) 2014 Kamil Baczkowicz * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * and Eclipse Distribution License v1.0 which accompany this distribution. * * The Eclipse Public License is available at * http://www.eclipse.org/legal/epl-v10.html * * The Eclipse Distribution License is available at * http://www.eclipse.org/org/documents/edl-v10.php. * * Contributors: * * Kamil Baczkowicz - initial API and implementation and/or initial documentation * */ package pl.baczkowicz.mqttspy.ui.events.queuable; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import javafx.application.Platform; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import pl.baczkowicz.mqttspy.messages.FormattedMqttMessage; import pl.baczkowicz.spy.eventbus.IKBus; import pl.baczkowicz.spy.storage.MessageList; import pl.baczkowicz.spy.ui.events.MessageAddedEvent; import pl.baczkowicz.spy.ui.events.MessageRemovedEvent; import pl.baczkowicz.spy.ui.events.queuable.EventQueueManager; import pl.baczkowicz.spy.ui.events.queuable.ui.BrowseReceivedMessageEvent; import pl.baczkowicz.spy.ui.events.queuable.ui.BrowseRemovedMessageEvent; import pl.baczkowicz.spy.ui.events.queuable.ui.SpyUIEvent; import pl.baczkowicz.spy.ui.events.queuable.ui.TopicSummaryNewMessageEvent; import pl.baczkowicz.spy.ui.events.queuable.ui.TopicSummaryRemovedMessageEvent; import pl.baczkowicz.spy.utils.ThreadingUtils; import pl.baczkowicz.spy.utils.TimeUtils; /** * This class is responsible for handling queued events. This is done in batches * for improved performance. So rather than flooding JavaFX with hundreds or * thousands of requests to do runLater, we buffer those events, and then * process them in batches. */ public class UIEventHandler implements Runnable { final static Logger logger = LoggerFactory.getLogger(UIEventHandler.class); private final EventQueueManager<FormattedMqttMessage> uiEventQueue; // private final EventManager<FormattedMqttMessage> eventManager; private IKBus eventBus; public UIEventHandler(final EventQueueManager<FormattedMqttMessage> uiEventQueue, final IKBus eventBus) { this.uiEventQueue = uiEventQueue; this.eventBus = eventBus; } @Override public void run() { ThreadingUtils.logThreadStarting("UI event handler"); while (true) { if (uiEventQueue.getEventCount() > 0) { showUpdates(); } // Sleep so that we don't run all the time - updating the UI 10 times a second should be more than enough if (ThreadingUtils.sleep(100)) { break; } } ThreadingUtils.logThreadEnding(); } private void showUpdates() { final long start = TimeUtils.getMonotonicTime(); long processed = 0; while (uiEventQueue.getEventCount() > 0) { final Map<String, List<SpyUIEvent<FormattedMqttMessage>>> events = uiEventQueue.getEvents(); { for (final String type : events.keySet()) { // Remove the event queue from the manager final List<SpyUIEvent<FormattedMqttMessage>> eventQueue = uiEventQueue.getAndRemoveEvents(type); if (eventQueue.isEmpty()) { continue; } processed = processed + eventQueue.size(); processEventType(eventQueue); } } } final long end = TimeUtils.getMonotonicTime(); if (logger.isTraceEnabled()) { logger.trace("UI event handling of {} items took {} ms", processed, (end - start)); } } private void processEventType(final List<SpyUIEvent<FormattedMqttMessage>> eventQueue) { // Split by parent final Map<MessageList<FormattedMqttMessage>, List<SpyUIEvent<FormattedMqttMessage>>> parentToEvent = new HashMap<>(); for (final SpyUIEvent<FormattedMqttMessage> event : eventQueue) { List<SpyUIEvent<FormattedMqttMessage>> parentQueue = parentToEvent.get(event.getList()); if (parentQueue == null) { parentQueue = new ArrayList<>(); parentToEvent.put(event.getList(), parentQueue); } parentToEvent.get(event.getList()).add(event); } // Process in batches for (final MessageList<FormattedMqttMessage> parent : parentToEvent.keySet()) { Platform.runLater(new Runnable() { @Override public void run() { handleEvents(parentToEvent.get(parent)); } }); } } @SuppressWarnings("unchecked") private void handleEvents(final List<SpyUIEvent<FormattedMqttMessage>> eventQueue) { final SpyUIEvent<FormattedMqttMessage> event = eventQueue.get(0); if (event instanceof BrowseReceivedMessageEvent) { eventBus.publish(new MessageAddedEvent<>((List<BrowseReceivedMessageEvent<FormattedMqttMessage>>)(Object)eventQueue, ((BrowseReceivedMessageEvent<FormattedMqttMessage>) event).getList())); // eventManager.notifyMessageAdded( // (List<BrowseReceivedMessageEvent<FormattedMqttMessage>>)(Object)eventQueue, // ((BrowseReceivedMessageEvent<FormattedMqttMessage>) event).getList()); } else if (event instanceof BrowseRemovedMessageEvent) { eventBus.publish(new MessageRemovedEvent<>((List<BrowseRemovedMessageEvent<FormattedMqttMessage>>)(Object)eventQueue, event.getList())); // eventManager.notifyMessageRemoved( // (List<BrowseRemovedMessageEvent<FormattedMqttMessage>>)(Object)eventQueue, // event.getList()); } else if (event instanceof TopicSummaryNewMessageEvent) { for (final SpyUIEvent<FormattedMqttMessage> item : eventQueue) { handleTopicSummaryNewMessageEvent((TopicSummaryNewMessageEvent<FormattedMqttMessage>) item); } } else if (event instanceof TopicSummaryRemovedMessageEvent) { for (final SpyUIEvent<FormattedMqttMessage> item : eventQueue) { handleTopicSummaryRemovedMessageEvent((TopicSummaryRemovedMessageEvent<FormattedMqttMessage>) item); } } } private void handleTopicSummaryNewMessageEvent(final TopicSummaryNewMessageEvent<FormattedMqttMessage> updateEvent) { // Calculate the overall message count per topic updateEvent.getList().getTopicSummary().addMessage(updateEvent.getAdded()); // Update the 'show' property if required if (updateEvent.isShowTopic()) { updateEvent.getList().getTopicSummary().setShowValue(updateEvent.getAdded().getTopic(), true); } } private void handleTopicSummaryRemovedMessageEvent(final TopicSummaryRemovedMessageEvent<FormattedMqttMessage> removeEvent) { // Remove old message from stats if (removeEvent.getRemoved() != null) { // TODO: does this actually work? removeEvent.getList().getTopicSummary().removeMessage(removeEvent.getRemoved()); } } }