]> gerrit.simantics Code Review - simantics/3d.git/blob - org.simantics.g3d.vtk/src/org/simantics/g3d/vtk/common/AbstractVTKNodeMap.java
Merge "Up-to-date resource classes"
[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 = true;
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         protected boolean filterChange(List<Pair<E,String>> list,E n) {
432             for (int i = list.size()-1; i >= 0; i--) {
433             if (list.get(i).first == n) {
434                 list.remove(i);
435                 return true;
436             }
437         }
438             return false;
439         }
440         
441         @SuppressWarnings("unchecked")
442         protected void updateCycle() {
443                 rem.clear();
444                 add.clear();
445                 mod.clear();
446                 propagation.clear();
447                 
448                 
449                 synchronized (syncMutex) {
450                     // Check for overlapping additions and deletions, prevent deleting objects that are also added and vice versa.
451                     Deque<E> stack = new ArrayDeque<E>();
452                 for (Pair<E, String> n : added) {
453                     stack.add(n.first);
454                 }
455                 while (!stack.isEmpty()) {
456                     E n = stack.pop();
457                     boolean conflict = filterChange(removed, n);
458                     if (conflict) {
459                         filterChange(added, n);   
460                     }
461                     if (n instanceof ParentNode) {
462                         ParentNode<INode> pn = (ParentNode<INode>)n;
463                         for (INode cn : pn.getNodes()) {
464                             stack.push((E)cn);
465                         }
466                     }
467                 }
468                 
469                         rem.addAll(removed);
470                         add.addAll(added);
471                         for (E e : updated.getKeys()) {
472                                 for (String s : updated.getValues(e)) {
473                                         mod.add(e, s);
474                                 }
475                         }
476                         syncDeletes();
477                         removed.clear();
478                         added.clear();
479                         updated.clear();
480                 }
481                 
482                 
483                 
484                 for (Pair<E, String> n : rem) {
485                         stopListening(n.first);
486                         removeActor(n.first);
487                 }
488                 
489                 for (Pair<E, String> n : add) {
490                         addActor(n.first);
491                         listen(n.first);
492                 }
493                 
494                 for (E e : mod.getKeys()) {
495                         Set<String> ids = mod.getValues(e);
496                         if (ids.contains(G3D.URIs.hasPosition) || ids.contains(G3D.URIs.hasOrientation)) {
497                                 if (!propagation.contains(e))
498                                         propagation.add(e);
499                         }
500                 }
501
502                 if (propagation.size() > 0) {
503                         stack.clear();
504                         stack.addAll(propagation);
505                         propagation.clear();
506                         while (!stack.isEmpty()) {
507                                 E node = stack.pop();
508                                 if (propagation.contains(node))
509                                         continue;
510                                 propagation.add(node);
511                                 for (NodeListener l : node.getListeners()) {
512                                         if (l == this) {
513                                                 //changeTracking = false;
514                                                 //l.propertyChanged(node, G3D.URIs.hasPosition);
515                                                 //changeTracking = true;
516                                         } else {
517                                                 l.propertyChanged(node, G3D.URIs.hasWorldPosition);
518                                         }
519                                 }
520                                 if (node instanceof ParentNode) {
521                                         stack.addAll(((ParentNode<E>)node).getNodes());
522                                 }
523                         }
524                 }
525                 
526 //      synchronized (syncMutex) {
527 //          rem.addAll(removed);
528 //          add.addAll(added);
529 //          //mod.addAll(updated);
530 //          for (E e : updated.getKeys()) {
531 //              for (String s : updated.getValues(e))
532 //                  mod.add(e, s);
533 //          }
534 //          
535 //          removed.clear();
536 //          added.clear();
537 //          updated.clear();
538 //      }
539                 
540                 for (E e : mod.getKeys()) {
541                         Set<String> ids = mod.getValues(e);
542                         updateActor(e,ids);
543                 }
544                 
545                 
546                 for (Pair<E, String> n : rem) {
547                         for (NodeListener l : nodeListeners)
548                                 l.nodeRemoved(null, n.first, n.second);
549                 }
550                 for (Pair<E, String> n : add) {
551                         for (NodeListener l : nodeListeners)
552                                 l.nodeAdded(n.first.getParent(), n.first, n.second);
553                 }
554 //      for (Pair<E, String> n : mod) {
555 //          for (NodeListener l : nodeListeners)
556 //              l.propertyChanged(n.first, n.second);
557 //      }
558                 for (E e : mod.getKeys()) {
559                         for (NodeListener l : nodeListeners)
560                                 for (String s : mod.getValues(e))
561                                         l.propertyChanged(e, s);
562                 }
563                 
564                 synchronized (syncMutex) {
565                         if (added.isEmpty() && removed.isEmpty() && updated.getKeys().size() == 0)
566                                 rangeModified = false;
567                 }
568         }
569         
570         @SuppressWarnings("unchecked")
571         private void listen(INode node) {
572                 node.addListener(this);
573                 if (node instanceof ParentNode<?>) {
574                         ParentNode<INode> parentNode = (ParentNode<INode>)node;
575                         for (INode n : parentNode.getNodes())
576                                 listen(n);
577                 }
578         }
579         
580         private void stopListening(INode node) {
581                 node.removeListener(this);
582                 if (node instanceof ParentNode<?>) {
583                         @SuppressWarnings("unchecked")
584                         ParentNode<INode> parentNode = (ParentNode<INode>)node;
585                         for (INode n : parentNode.getNodes())
586                                 stopListening(n);
587                 }
588         }
589         
590         @SuppressWarnings("unchecked")
591         @Override
592         public void propertyChanged(INode node, String id) {
593                 //receiveUpdate((E)node, id, graphUpdates);
594                 receiveUpdate((E)node, id, graphModified.contains(node));
595                 
596         }
597         
598         @SuppressWarnings("unchecked")
599         @Override
600         public <T extends INode> void nodeAdded(ParentNode<T> node, INode child,
601                         String rel) {
602                 if (DEBUG) System.out.println("Node added " + child + " parent " + node);
603                 //receiveAdd((E)child, rel ,graphUpdates);
604                 receiveAdd((E)child, rel ,graphModified.contains(node));
605                 
606         }
607         
608         @SuppressWarnings("unchecked")
609         @Override
610         public <T extends INode> void nodeRemoved(ParentNode<T> node, INode child,
611                         String rel) {
612                 if (DEBUG) System.out.println("Node removed " + child + " parent " + node);
613                 //receiveRemove((E)child, rel, graphUpdates);
614                 receiveRemove((E)child, rel, graphModified.contains(node));
615                 
616                 //FIXME : sometimes removed structural models cause ObjMap to add their children again.
617                 //        removing the listener here prevents corruption of visual model, but better fix is needed.
618                 stopListening(child);
619         }
620         
621         @Override
622         public void delete() {
623                 if (undoRedoSupport != null)
624                         undoRedoSupport.cancel(this);
625                 
626                 changeTracking = false;
627                 view.removeListener(this);
628                 mapping.removeMappingListener(this);
629
630                 List<E> nodes = new ArrayList<E>(nodeToActor.getKeySize());
631                 nodes.addAll(nodeToActor.getKeys());
632                 for (E node : nodes) {
633                         node.removeListener(this);
634                         removeActor(node);
635                         node.cleanup();
636                 }
637                 for (vtkProp prop : actorToNode.keySet()) {
638                         if (prop.GetVTKId() != 0) 
639                                 prop.Delete();
640                 }
641                 actorToNode.clear();
642                 nodeToActor.clear();
643                 
644         }
645         
646         
647         private List<NodeListener> nodeListeners = new ArrayList<NodeListener>();
648         @Override
649         public void addListener(NodeListener listener) {
650                 nodeListeners.add(listener);
651                 
652         }
653         
654         @Override
655         public void removeListener(NodeListener listener) {
656                 nodeListeners.remove(listener);
657                 
658         }
659         
660         public IMapping<Object,E> getMapping() {
661                 return mapping;
662         }
663         
664         
665 }