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