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