]> gerrit.simantics Code Review - simantics/platform.git/blob - bundles/org.simantics.g2d/src/org/simantics/g2d/diagram/participant/DelayedBatchElementPainter.java
Still working for multiple readers
[simantics/platform.git] / bundles / org.simantics.g2d / src / org / simantics / g2d / diagram / participant / DelayedBatchElementPainter.java
1 /*******************************************************************************
2  * Copyright (c) 2007, 2010 Association for Decentralized Information Management
3  * in Industry THTH ry.
4  * All rights reserved. This program and the accompanying materials
5  * are made available under the terms of the Eclipse Public License v1.0
6  * which accompanies this distribution, and is available at
7  * http://www.eclipse.org/legal/epl-v10.html
8  *
9  * Contributors:
10  *     VTT Technical Research Centre of Finland - initial API and implementation
11  *******************************************************************************/
12 package org.simantics.g2d.diagram.participant;
13
14 import java.util.HashSet;
15 import java.util.Set;
16 import java.util.concurrent.ScheduledExecutorService;
17 import java.util.concurrent.ScheduledFuture;
18 import java.util.concurrent.TimeUnit;
19
20 import org.simantics.g2d.canvas.Hints;
21 import org.simantics.g2d.canvas.ICanvasContext;
22 import org.simantics.g2d.diagram.IDiagram;
23 import org.simantics.g2d.diagram.IDiagram.CompositionListener;
24 import org.simantics.g2d.diagram.handler.PickRequest.PickFilter;
25 import org.simantics.g2d.element.IElement;
26 import org.simantics.g2d.internal.DebugPolicy;
27 import org.simantics.utils.datastructures.hints.IHintContext.Key;
28 import org.simantics.utils.datastructures.hints.IHintListener;
29 import org.simantics.utils.datastructures.hints.IHintObservable;
30 import org.simantics.utils.threads.ThreadUtils;
31
32 /**
33  * A diagram participant that is meant for keeping a selected set of diagram
34  * elements up-to-date. The difference to {@link ElementPainter} is that this
35  * participant throttles updates so that the selected set of elements is updated
36  * once in a specified time interval.
37  * 
38  * <p>
39  * This class listens to {@link Hints#KEY_DIRTY} hint of elements accepted by
40  * the specified {@link #elementFilter}. If the key receives a value of
41  * {@link Hints#VALUE_SG_DELAYED_UPDATE}, this class will react by queueing the
42  * element to be updated after the current delay period is over. The delay
43  * period ensures that existing elements are not updated more frequently than
44  * the specified time period. When it becomes time to perform the actual
45  * updates, this class will set the {@link Hints#KEY_DIRTY} hint of all
46  * currently dirty elements to value {@link Hints#VALUE_SG_DIRTY}. This will
47  * signal other listeners ({@link ElementPainter}) to react.
48  * 
49  * @author Tuukka Lehtonen
50  */
51 public class DelayedBatchElementPainter extends AbstractDiagramParticipant {
52
53     private static final boolean DEBUG          = DebugPolicy.DEBUG_DELAYED_ELEMENT_PAINTER;
54     private static final boolean DEBUG_MARKING  = DebugPolicy.DEBUG_DELAYED_ELEMENT_PAINTER_MARKING;
55
56     ScheduledExecutorService     executor       = ThreadUtils.getNonBlockingWorkExecutor();
57
58     PickFilter                   elementFilter;
59     long                         delay;
60     TimeUnit                     delayUnit;
61
62     Set<IElement>                dirtyElements  = new HashSet<IElement>();
63     volatile ScheduledFuture<?>  scheduled      = null;
64     long                         lastUpdateTime = 0;
65
66     class ElementListener implements CompositionListener, IHintListener {
67         @Override
68         public void onElementAdded(IDiagram d, IElement e) {
69             if (DEBUG)
70                 debug("onElementAdded(%s, %s)\n", d, e);
71             if (elementFilter.accept(e))
72                 addElement(e);
73         }
74
75         @Override
76         public void onElementRemoved(IDiagram d, IElement e) {
77             if (DEBUG)
78                 debug("onElementRemoved(%s, %s)\n", d, e);
79             if (elementFilter.accept(e))
80                 removeElement(e);
81         }
82
83         @Override
84         public void hintChanged(IHintObservable sender, Key key, Object oldValue, Object newValue) {
85             if (key == Hints.KEY_DIRTY && newValue == Hints.VALUE_SG_DELAYED_UPDATE && sender instanceof IElement) {
86                 markDirty((IElement) sender);
87             }
88         }
89
90         @Override
91         public void hintRemoved(IHintObservable sender, Key key, Object oldValue) {
92         }
93     }
94
95     private final ElementListener elementListener = new ElementListener();
96
97     public DelayedBatchElementPainter(PickFilter elementFilter, long delay, TimeUnit delayUnit) {
98         this.elementFilter = elementFilter;
99         this.delay = delay;
100         this.delayUnit = delayUnit;
101     }
102
103     @Override
104     public void removedFromContext(ICanvasContext ctx) {
105         ScheduledFuture<?> s = scheduled;
106         if (s != null)
107             s.cancel(false);
108
109         super.removedFromContext(ctx);
110     }
111
112     @Override
113     protected void onDiagramSet(IDiagram newValue, IDiagram oldValue) {
114         if (oldValue == newValue)
115             return;
116
117         if (oldValue != null) {
118             for (IElement e : oldValue.getElements()) {
119                 if (elementFilter.accept(e))
120                     removeElement(e);
121             }
122             oldValue.removeCompositionListener(elementListener);
123         }
124
125         if (newValue != null) {
126             for (IElement e : newValue.getElements()) {
127                 if (elementFilter.accept(e))
128                     addElement(e);
129             }
130             newValue.addCompositionListener(elementListener);
131         }
132     }
133
134     protected void addElement(IElement e) {
135         if (DEBUG)
136             debug("addElement(%s)\n", e);
137         e.addKeyHintListener(Hints.KEY_DIRTY, elementListener);
138     }
139
140     protected void removeElement(IElement e) {
141         if (DEBUG)
142             debug("removeElement(%s)\n", e);
143         e.removeKeyHintListener(Hints.KEY_DIRTY, elementListener);
144     }
145
146     protected void markDirty(IElement e) {
147         if (DEBUG_MARKING)
148             debug("Marking element dirty: %s\n", e);
149         dirtyElements.add(e);
150         scheduleUpdate();
151     }
152
153     private Runnable updater = new Runnable() {
154         @Override
155         public void run() {
156             if (DEBUG)
157                 debug("marking %d elements dirty\n", dirtyElements.size());
158             for (IElement e : dirtyElements)
159                 e.setHint(Hints.KEY_DIRTY, Hints.VALUE_SG_DIRTY);
160             dirtyElements.clear();
161             scheduled = null;
162             lastUpdateTime = System.currentTimeMillis();
163             if (DEBUG)
164                 debug("marking last update time %d\n", lastUpdateTime);
165
166             if (!isRemoved())
167                 setDirty();
168         }
169     };
170
171     private Runnable delayedUpdater = new Runnable() {
172         @Override
173         public void run() {
174             if (DEBUG)
175                 debug("scheduling updater\n");
176             asyncExec(updater);
177         }
178     };
179
180     private void scheduleUpdate() {
181         if (scheduled == null) {
182             long timeNow = System.currentTimeMillis();
183             long timeSinceLastUpdate = timeNow - lastUpdateTime;
184             long requestedDelay = delayUnit.toMillis(delay);
185             long scheduleDelay = Math.max(0, Math.min(requestedDelay, requestedDelay - timeSinceLastUpdate));
186
187             if (DEBUG)
188                 debug("scheduling update with delay %dms (time=%d, time passed=%dms)\n", scheduleDelay, timeNow, timeSinceLastUpdate);
189
190             if (scheduleDelay == 0) {
191                 asyncExec(updater);
192             } else {
193                 scheduled = executor.schedule(delayedUpdater, scheduleDelay, TimeUnit.MILLISECONDS);
194             }
195         }
196     }
197
198
199     private void debug(String format, Object... args) {
200         if (DEBUG) {
201             System.out.format(getClass().getSimpleName()
202                     + "[filter=" + elementFilter
203                     + ", delay=" + delayUnit.toMillis(delay) + "ms] "
204                     + format, args);
205         }
206     }
207
208 }