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