]> gerrit.simantics Code Review - simantics/3d.git/blob - org.simantics.g3d.vtk/src/org/simantics/g3d/vtk/common/AbstractVTKNodeMap.java
ConstraintDetector / Snapping tool for pipeline routing
[simantics/3d.git] / org.simantics.g3d.vtk / src / org / simantics / g3d / vtk / common / AbstractVTKNodeMap.java
1 /*******************************************************************************
2  * Copyright (c) 2012, 2013 Association for Decentralized Information Management in
3  * 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.g3d.vtk.common;
13
14 import java.util.ArrayList;
15 import java.util.Collection;
16 import java.util.Collections;
17 import java.util.HashMap;
18 import java.util.HashSet;
19 import java.util.List;
20 import java.util.Map;
21 import java.util.Set;
22 import java.util.Stack;
23
24 import org.simantics.db.ReadGraph;
25 import org.simantics.db.Session;
26 import org.simantics.db.UndoContext;
27 import org.simantics.db.WriteGraph;
28 import org.simantics.db.common.request.ReadRequest;
29 import org.simantics.db.common.request.WriteRequest;
30 import org.simantics.db.exception.DatabaseException;
31 import org.simantics.db.layer0.util.Layer0Utils;
32 import org.simantics.db.service.UndoRedoSupport;
33 import org.simantics.g3d.ontology.G3D;
34 import org.simantics.g3d.scenegraph.RenderListener;
35 import org.simantics.g3d.scenegraph.base.INode;
36 import org.simantics.g3d.scenegraph.base.NodeListener;
37 import org.simantics.g3d.scenegraph.base.ParentNode;
38 import org.simantics.objmap.exceptions.MappingException;
39 import org.simantics.objmap.graph.IMapping;
40 import org.simantics.objmap.graph.IMappingListener;
41 import org.simantics.utils.datastructures.Callback;
42 import org.simantics.utils.datastructures.MapList;
43 import org.simantics.utils.datastructures.MapSet;
44 import org.simantics.utils.datastructures.Pair;
45 import org.simantics.utils.ui.ExceptionUtils;
46
47 import vtk.vtkProp;
48
49 public abstract class AbstractVTKNodeMap<E extends INode> implements VTKNodeMap<E>, IMappingListener, RenderListener, NodeListener, UndoRedoSupport.ChangeListener{
50
51         private static final boolean DEBUG = false;
52         
53         protected Session session;
54         protected IMapping<Object,E> mapping;
55         protected VtkView view;
56         
57         protected MapList<E, vtkProp> nodeToActor = new MapList<E, vtkProp>();
58         protected Map<vtkProp,E> actorToNode = new HashMap<vtkProp, E>();
59
60         protected ParentNode<E> rootNode;
61         
62         protected UndoRedoSupport undoRedoSupport;
63         protected int undoOpCount = 0;
64         protected int redoOpCount = 0;
65         protected boolean runUndo = false;
66         protected boolean runRedo = false;
67         public AbstractVTKNodeMap(Session session, IMapping<Object,E> mapping, VtkView view, ParentNode<E> rootNode) {
68                 this.session = session;
69                 this.mapping = mapping;
70                 this.view = view;
71                 this.rootNode = rootNode;
72                 view.addListener(this);
73                 mapping.addMappingListener(this);
74                 rootNode.addListener(this);
75                 
76                 undoRedoSupport = session.getService(UndoRedoSupport.class);
77                 undoRedoSupport.subscribe(this);
78                 try {
79                         UndoContext undoContext = undoRedoSupport.getUndoContext(session); 
80                         undoOpCount = undoContext.getAll().size();
81                         redoOpCount = undoContext.getRedoList().size();
82                 } catch(DatabaseException e) {
83                         e.printStackTrace();
84                 }
85         }
86         
87         
88         
89         protected abstract void addActor(E node);
90         protected abstract void removeActor(E node);
91         protected abstract void updateActor(E node,Set<String> ids);
92         
93         public void repaint() {
94                 view.refresh();
95         }
96         
97         public void populate() {
98                 for (E node : rootNode.getNodes()) {
99                         receiveAdd(node, node.getParentRel(),true);
100                 }
101                 repaint();
102         }
103         
104         @Override
105         public E getNode(vtkProp prop) {
106                 return actorToNode.get(prop);
107         }
108         
109         @SuppressWarnings("unchecked")
110         @Override
111         public Collection<vtkProp> getRenderObjects(INode node) {
112                 return nodeToActor.getValues((E)node);
113         }
114         
115         @SuppressWarnings("unchecked")
116         @Override
117         public ParentNode<E> getRootNode() {
118                 return (ParentNode<E>)rootNode;
119         }
120         
121         
122         
123         @Override
124         public boolean isChangeTracking() {
125                 return changeTracking;
126         }
127         
128         @Override
129         public void setChangeTracking(boolean enabled) {
130                 changeTracking = enabled;
131         }
132         
133         private boolean changeTracking = true;
134         
135         protected Object syncMutex = new Object(); 
136         
137
138         private List<Pair<E,String>> added = new ArrayList<Pair<E,String>>();
139         private List<Pair<E,String>> removed = new ArrayList<Pair<E,String>>();
140         private MapSet<E, String> updated = new MapSet.Hash<E, String>();
141
142         private boolean rangeModified = false;
143         
144         @Override
145         public void onChanged() {
146                 try {
147                         UndoContext undoContext = undoRedoSupport.getUndoContext(session);
148                         int ucount = undoContext.getAll().size();
149                         int rcount = undoContext.getRedoList().size();
150                         if (DEBUG) System.out.println("Previous U:" + undoOpCount +" R:" + redoOpCount +" Current U:"+ucount+" R:"+rcount);
151                         if (ucount < undoOpCount) {
152                                 runUndo = true;
153                         } else {
154                                 runUndo = false;
155                         }
156                         if (!runUndo && rcount > 0)
157                                 runRedo = true;
158                         else
159                                 runRedo = false;
160                         undoOpCount = ucount;
161                         redoOpCount = rcount;
162                         
163                         if (DEBUG) System.out.println("Undo " + runUndo + " Redo " + runRedo);
164                 } catch (DatabaseException e) {
165                         // TODO Auto-generated catch block
166                         e.printStackTrace();
167                 }
168                 
169                 
170         }
171         
172         @SuppressWarnings("unchecked")
173         @Override
174         public void updateRenderObjectsFor(E node) {
175                 List<vtkProp> toDelete = new ArrayList<vtkProp>();
176                 view.lock();
177                 for (vtkProp prop : nodeToActor.getValues((E)node)) {
178                         if (prop.GetVTKId() != 0) {
179                                 view.getRenderer().RemoveActor(prop);
180                                 //prop.Delete();
181                                 toDelete.add(prop);
182                         }
183                         actorToNode.remove(prop);
184                 }
185                 view.unlock();
186                 nodeToActor.remove((E)node);
187                 Collection<vtkProp> coll = getActors((E)node);
188                 if (coll != null) {
189                         for (vtkProp prop : coll) {
190                                 nodeToActor.add((E)node,prop);
191                                 actorToNode.put(prop, (E)node);
192                                 toDelete.remove(prop);
193                         }
194                 }
195                 for (vtkProp p : toDelete)
196                         p.Delete();
197         }
198         
199         protected abstract  Collection<vtkProp> getActors(E node);
200         
201         @SuppressWarnings("unchecked")
202         private void receiveAdd(E node, String id, boolean db) {
203                 if (DEBUG) System.out.println("receiveAdd " + debugString(node)  + " " + id + " " + db);
204                 synchronized (syncMutex) {
205                         for (Pair<E, String> n : added) {
206                                 if (n.first.equals(node))
207                                         return;
208                         }
209                         if (changeTracking) {
210                                 mapping.rangeModified((E)node.getParent());
211                         }
212                         added.add(new Pair<E, String>(node, id));
213                         rangeModified = true;
214                 }
215                 view.refresh();
216         }
217         
218         @SuppressWarnings("unchecked")
219         private void receiveRemove(E node, String id, boolean db) {
220                 if (DEBUG) System.out.println("receiveRemove " + debugString(node)  + " " + id + " " + db);
221                 synchronized (syncMutex) {
222                         for (Pair<E, String> n : removed) {
223                                 if (n.first.equals(node))
224                                         return;
225                         }
226                         if (changeTracking && !db)
227                                 mapping.rangeModified((E)node.getParent());
228                         removed.add(new Pair<E, String>(node, id));
229                         rangeModified = true;
230                 }
231                 repaint();
232         }
233         
234         @SuppressWarnings("unchecked")
235         private void receiveUpdate(E node, String id, boolean db) {
236                 if (DEBUG) System.out.println("receiveUpdate " + debugString(node)  + " " + id + " " + db);
237                 synchronized (syncMutex) {
238 //          for (Pair<E, String> n : updated) {
239 //              if (n.first.equals(node))
240 //                  return;
241 //          }
242                         if (changeTracking && !db)
243                                 mapping.rangeModified(node);
244                         //updated.add(new Pair<E, String>(node, id));
245                         updated.add(node, id);
246                         rangeModified = true;
247                 }
248                 repaint();
249         }
250         
251         private boolean graphUpdates = false;
252         private Set<E> graphModified = new HashSet<E>();
253         
254         private boolean requestCommit = false;
255         private String commitMessage = null;
256         
257         @Override
258         public void commit(String message) {
259                 requestCommit = true;
260                 commitMessage = message;
261         }
262         
263         protected void doCommit() {
264                 session.asyncRequest(new WriteRequest() {
265                         
266                         @Override
267                         public void perform(WriteGraph graph) throws DatabaseException {
268                                 if (DEBUG) System.out.println("Commit " + commitMessage);
269                                 if (commitMessage != null) {
270                                         Layer0Utils.addCommentMetadata(graph, commitMessage);
271                                         graph.markUndoPoint();
272                                         commitMessage = null;
273                                 }
274                                 commit(graph);
275                         }
276                         
277                 }, new Callback<DatabaseException>() {
278                         
279                         @Override
280                         public void run(DatabaseException parameter) {
281                                 if (parameter != null)
282                                         ExceptionUtils.logAndShowError("Cannot commit editor changes", parameter);
283                         }
284                 });
285         }
286         
287         protected void commit(WriteGraph graph) throws DatabaseException {
288                 synchronized(syncMutex) {
289                         if (DEBUG) System.out.println("Commit");
290                         graphUpdates = true;
291                         mapping.updateDomain(graph);
292                         graphUpdates = false;
293                         clearDeletes();
294                         if (DEBUG) System.out.println("Commit done");
295                 }
296         }
297         
298         
299         
300         @Override
301         public void domainModified() {
302                 if (graphUpdates)
303                         return;
304                 if (DEBUG)System.out.println("domainModified");
305                 session.asyncRequest(new ReadRequest() {
306                         
307                         @SuppressWarnings("unchecked")
308                         @Override
309                         public void run(ReadGraph graph) throws DatabaseException {
310                                 update(graph);
311                         }
312                 });
313                 
314         }
315         
316         protected void reset(ReadGraph graph) throws MappingException {
317                 if (DEBUG) System.out.println("Reset");
318                 synchronized (syncMutex) {
319                         graphUpdates = true;
320                         mapping.getRangeModified().clear();
321                         for (Object o : mapping.getDomain())
322                                 mapping.domainModified(o);
323                         mapping.updateRange(graph);
324                         graphModified.clear();
325                         graphUpdates = false;
326                 }
327         }
328         
329         private boolean useFullSyncWithUndo = false;
330         
331         protected void update(ReadGraph graph) throws DatabaseException {
332                 if (DEBUG) System.out.println("Graph update start");
333                 if (runUndo && useFullSyncWithUndo) {
334                         reset(graph);
335                 } else {
336                         synchronized (syncMutex) {
337                                 graphUpdates = true;
338                                 for (Object domainObject : mapping.getDomainModified()) {
339                                         E rangeObject = mapping.get(domainObject);
340                                         if (rangeObject != null)
341                                                 graphModified.add(rangeObject);
342                                 }
343                                 mapping.updateRange(graph);
344                                 graphModified.clear();
345                                 syncDeletes();
346                                 clearDeletes();
347                                 graphUpdates = false;
348                         }
349                 }
350                 
351                 if (mapping.isRangeModified() && !runUndo && !runRedo)
352                         commit((String)null);
353                 if (DEBUG) System.out.println("Graph update done");
354         }
355         
356         @Override
357         public void rangeModified() {
358                 //System.out.println("rangeModified");
359
360         }
361         
362         @Override
363         public void postRender() {
364                 // Commit changes if
365                 // 1. Commit has been requested
366                 // 2. There are no pending changes that should be processed in preRender() 
367                 if (requestCommit && !rangeModified) { // FIXME : not thread safe.
368                         requestCommit = false;
369                         doCommit();
370                 }
371         }
372         
373         // Reusable containers for data synchronisation
374         List<Pair<E, String>> rem = new ArrayList<Pair<E,String>>();  // Removed objects
375         List<Pair<E, String>> add = new ArrayList<Pair<E,String>>();  // Added objects
376         MapSet<E, String> mod = new MapSet.Hash<E, String>();         // Modified objects
377         Set<E> propagation = new HashSet<E>();                        // Objects with propagated changes 
378         Stack<E> stack = new Stack<E>();                              // Stack for handling propagation
379         Set<E> delete = Collections.synchronizedSet(new HashSet<E>()); // Objects to be completely deleted
380         Set<E> deleteUC = new HashSet<E>();
381         
382         @Override
383         public synchronized void preRender() {
384                 updateCycle();
385         }
386         
387         
388         /**
389          * When objects are removed (either from Java or Graph), after remove processing the Java objects remain in mapping cache.
390          * This causes problems with Undo and Redo, whcih the end up re-using the removed objects from mapping cache.
391          * 
392          * This code here synchronizes removed and added objects to collect deletable objects. (a deletable object is one which is removed but not added).  
393          * 
394          */
395         protected void syncDeletes() {
396                 deleteUC.clear();
397                 for (Pair<E, String> n : removed) {
398                         deleteUC.add(n.first);   
399                 }
400                 for (Pair<E, String> n : added) {
401                         deleteUC.remove(n.first);   
402                 } 
403                 if (DEBUG && deleteUC.size() > 0) {
404                         System.out.println("Delete sync");
405                         for (E n : delete) {
406                                 System.out.println(debugString(n));
407                         }
408                 }
409                 delete.addAll(deleteUC);
410                 deleteUC.clear();
411         }
412
413         /**
414          * Clears deletable objects from mapping cache.
415          */
416         protected void clearDeletes() {
417                 if (DEBUG && delete.size() > 0) System.out.println("Delete");
418                 for (E n : delete) {
419                         if (DEBUG) System.out.println(debugString(n));
420                         mapping.getRange().remove(n);
421                 }
422                 delete.clear();
423         }
424         
425         protected String debugString(E n) {
426                 return n + "@" + Integer.toHexString(n.hashCode());
427         }
428         
429         @SuppressWarnings("unchecked")
430         protected void updateCycle() {
431                 rem.clear();
432                 add.clear();
433                 mod.clear();
434                 propagation.clear();
435                 
436                 
437                 synchronized (syncMutex) {
438                         rem.addAll(removed);
439                         add.addAll(added);
440                         for (E e : updated.getKeys()) {
441                                 for (String s : updated.getValues(e)) {
442                                         mod.add(e, s);
443                                 }
444                         }
445                         syncDeletes();
446                         removed.clear();
447                         added.clear();
448                         updated.clear();
449                 }
450                 
451                 for (Pair<E, String> n : rem) {
452                         stopListening(n.first);
453                         removeActor(n.first);
454                 }
455                 
456                 for (Pair<E, String> n : add) {
457                         addActor(n.first);
458                         listen(n.first);
459                 }
460                 
461                 for (E e : mod.getKeys()) {
462                         Set<String> ids = mod.getValues(e);
463                         if (ids.contains(G3D.URIs.hasPosition) || ids.contains(G3D.URIs.hasOrientation)) {
464                                 if (!propagation.contains(e))
465                                         propagation.add(e);
466                         }
467                 }
468
469                 if (propagation.size() > 0) {
470                         stack.clear();
471                         stack.addAll(propagation);
472                         propagation.clear();
473                         while (!stack.isEmpty()) {
474                                 E node = stack.pop();
475                                 if (propagation.contains(node))
476                                         continue;
477                                 propagation.add(node);
478                                 for (NodeListener l : node.getListeners()) {
479                                         if (l == this) {
480                                                 //changeTracking = false;
481                                                 //l.propertyChanged(node, G3D.URIs.hasPosition);
482                                                 //changeTracking = true;
483                                         } else {
484                                                 l.propertyChanged(node, G3D.URIs.hasWorldPosition);
485                                         }
486                                 }
487                                 if (node instanceof ParentNode) {
488                                         stack.addAll(((ParentNode<E>)node).getNodes());
489                                 }
490                         }
491                 }
492                 
493 //      synchronized (syncMutex) {
494 //          rem.addAll(removed);
495 //          add.addAll(added);
496 //          //mod.addAll(updated);
497 //          for (E e : updated.getKeys()) {
498 //              for (String s : updated.getValues(e))
499 //                  mod.add(e, s);
500 //          }
501 //          
502 //          removed.clear();
503 //          added.clear();
504 //          updated.clear();
505 //      }
506                 
507                 for (E e : mod.getKeys()) {
508                         Set<String> ids = mod.getValues(e);
509                         updateActor(e,ids);
510                 }
511                 
512                 
513                 for (Pair<E, String> n : rem) {
514                         for (NodeListener l : nodeListeners)
515                                 l.nodeRemoved(null, n.first, n.second);
516                 }
517                 for (Pair<E, String> n : add) {
518                         for (NodeListener l : nodeListeners)
519                                 l.nodeAdded(n.first.getParent(), n.first, n.second);
520                 }
521 //      for (Pair<E, String> n : mod) {
522 //          for (NodeListener l : nodeListeners)
523 //              l.propertyChanged(n.first, n.second);
524 //      }
525                 for (E e : mod.getKeys()) {
526                         for (NodeListener l : nodeListeners)
527                                 for (String s : mod.getValues(e))
528                                         l.propertyChanged(e, s);
529                 }
530                 
531                 synchronized (syncMutex) {
532                         if (added.isEmpty() && removed.isEmpty() && updated.getKeys().size() == 0)
533                                 rangeModified = false;
534                 }
535         }
536         
537         @SuppressWarnings("unchecked")
538         private void listen(INode node) {
539                 node.addListener(this);
540                 if (node instanceof ParentNode<?>) {
541                         ParentNode<INode> parentNode = (ParentNode<INode>)node;
542                         for (INode n : parentNode.getNodes())
543                                 listen(n);
544                 }
545         }
546         
547         private void stopListening(INode node) {
548                 node.removeListener(this);
549                 if (node instanceof ParentNode<?>) {
550                         @SuppressWarnings("unchecked")
551                         ParentNode<INode> parentNode = (ParentNode<INode>)node;
552                         for (INode n : parentNode.getNodes())
553                                 stopListening(n);
554                 }
555         }
556         
557         @SuppressWarnings("unchecked")
558         @Override
559         public void propertyChanged(INode node, String id) {
560                 //receiveUpdate((E)node, id, graphUpdates);
561                 receiveUpdate((E)node, id, graphModified.contains(node));
562                 
563         }
564         
565         @SuppressWarnings("unchecked")
566         @Override
567         public <T extends INode> void nodeAdded(ParentNode<T> node, INode child,
568                         String rel) {
569                 if (DEBUG) System.out.println("Node added " + child + " parent " + node);
570                 //receiveAdd((E)child, rel ,graphUpdates);
571                 receiveAdd((E)child, rel ,graphModified.contains(node));
572                 
573         }
574         
575         @SuppressWarnings("unchecked")
576         @Override
577         public <T extends INode> void nodeRemoved(ParentNode<T> node, INode child,
578                         String rel) {
579                 if (DEBUG) System.out.println("Node removed " + child + " parent " + node);
580                 //receiveRemove((E)child, rel, graphUpdates);
581                 receiveRemove((E)child, rel, graphModified.contains(node));
582                 
583                 //FIXME : sometimes removed structural models cause ObjMap to add their children again.
584                 //        removing the listener here prevents corruption of visual model, but better fix is needed.
585                 stopListening(child);
586         }
587         
588         @Override
589         public void delete() {
590                 if (undoRedoSupport != null)
591                         undoRedoSupport.cancel(this);
592                 
593                 changeTracking = false;
594                 view.removeListener(this);
595                 mapping.removeMappingListener(this);
596
597                 List<E> nodes = new ArrayList<E>(nodeToActor.getKeySize());
598                 nodes.addAll(nodeToActor.getKeys());
599                 for (E node : nodes) {
600                         node.removeListener(this);
601                         removeActor(node);
602                         node.cleanup();
603                 }
604                 for (vtkProp prop : actorToNode.keySet()) {
605                         if (prop.GetVTKId() != 0) 
606                                 prop.Delete();
607                 }
608                 actorToNode.clear();
609                 nodeToActor.clear();
610                 
611         }
612         
613         
614         private List<NodeListener> nodeListeners = new ArrayList<NodeListener>();
615         @Override
616         public void addListener(NodeListener listener) {
617                 nodeListeners.add(listener);
618                 
619         }
620         
621         @Override
622         public void removeListener(NodeListener listener) {
623                 nodeListeners.remove(listener);
624                 
625         }
626         
627         public IMapping<Object,E> getMapping() {
628                 return mapping;
629         }
630         
631         
632 }