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