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