]> gerrit.simantics Code Review - simantics/3d.git/blob - org.simantics.g3d/src/org/simantics/g3d/scl/ScriptNodeMap.java
4af8e5a0b829a8e3caf2154c3f490db49da7e8ad
[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         dirty = true;
229 //        session.asyncRequest(new ReadRequest() {
230 //            
231 //            @SuppressWarnings("unchecked")
232 //            @Override
233 //            public void run(ReadGraph graph) throws DatabaseException {
234 //                update(graph);
235 //            }
236 //        });
237         
238     }
239     
240     protected void reset(ReadGraph graph) throws MappingException {
241         if (DEBUG) System.out.println("Reset");
242         synchronized (syncMutex) {
243             graphUpdates = true;
244             mapping.getRangeModified().clear();
245             for (Object o : mapping.getDomain())
246                 mapping.domainModified(o);
247             mapping.updateRange(graph);
248             graphModified.clear();
249             graphUpdates = false;
250         }
251     }
252     
253        
254     protected void update(ReadGraph graph) throws DatabaseException {
255         if (DEBUG) System.out.println("Graph update start");
256         synchronized (syncMutex) {
257             graphUpdates = true;
258             for (Object domainObject : mapping.getDomainModified()) {
259                 E rangeObject = mapping.get(domainObject);
260                 if (rangeObject != null)
261                     graphModified.add(rangeObject);
262             }
263             mapping.updateRange(graph);
264             graphModified.clear();
265             syncDeletes();
266             clearDeletes();
267             graphUpdates = false;
268         }
269         
270         
271         //if (mapping.isRangeModified() && !runUndo) // FIXME : redo?
272         if (mapping.isRangeModified())
273             commit((String)null);
274         if (DEBUG) System.out.println("Graph update done");
275     }
276     
277     @Override
278     public void rangeModified() {
279         //System.out.println("rangeModified");
280
281     }
282     
283     public void update() throws DatabaseException{
284         while (dirty) {
285             dirty = false;
286             // preRender
287             updateCycle();
288             // postRender
289             if (requestCommit && !rangeModified) { // FIXME : not thread safe.
290                 requestCommit = false;
291                 doCommit();
292             }
293             session.syncRequest(new ReadRequest() { 
294               @SuppressWarnings("unchecked")
295               @Override
296               public void run(ReadGraph graph) throws DatabaseException {
297                   update(graph);
298               }
299             });
300         }
301     }
302     
303
304     
305     // Reusable containers for data synchronisation
306     List<Pair<E, String>> rem = new ArrayList<Pair<E,String>>();  // Removed objects
307     List<Pair<E, String>> add = new ArrayList<Pair<E,String>>();  // Added objects
308     MapSet<E, String> mod = new MapSet.Hash<E, String>();         // Modified objects
309     Set<E> propagation = new HashSet<E>();                        // Objects with propagated changes 
310     Stack<E> stack = new Stack<E>();                              // Stack for handling propagation
311     Set<E> delete = Collections.synchronizedSet(new HashSet<E>()); // Objects to be completely deleted
312     Set<E> deleteUC = new HashSet<E>();
313     
314    
315     
316     
317     /**
318      * When objects are removed (either from Java or Graph), after remove processing the Java objects remain in mapping cache.
319      * This causes problems with Undo and Redo, whcih the end up re-using the removed objects from mapping cache.
320      * 
321      * This code here synchronizes removed and added objects to collect deletable objects. (a deletable object is one which is removed but not added).  
322      * 
323      */
324     protected void syncDeletes() {
325         deleteUC.clear();
326         for (Pair<E, String> n : removed) {
327             deleteUC.add(n.first);   
328          }
329          for (Pair<E, String> n : added) {
330              deleteUC.remove(n.first);   
331          } 
332          if (DEBUG && deleteUC.size() > 0) {
333              System.out.println("Delete sync");
334              for (E n : delete) {
335                  System.out.println(debugString(n));
336              }
337          }
338          delete.addAll(deleteUC);
339          deleteUC.clear();
340     }
341     
342     /**
343      * Clears deletable objects from mapping cache.
344      */
345     protected void clearDeletes() {
346         if (DEBUG && delete.size() > 0) System.out.println("Delete");
347         for (E n : delete) {
348             if (DEBUG) System.out.println(debugString(n));
349             mapping.getRange().remove(n);
350         }
351         delete.clear();
352     }
353     
354     protected String debugString(E n) {
355         return n + "@" + Integer.toHexString(n.hashCode());
356     }
357     
358     @SuppressWarnings("unchecked")
359     protected void updateCycle() {
360         rem.clear();
361         add.clear();
362         mod.clear();
363         propagation.clear();
364         
365         
366         synchronized (syncMutex) {
367             rem.addAll(removed);
368             add.addAll(added);
369             for (E e : updated.getKeys()) {
370                 for (String s : updated.getValues(e)) {
371                     mod.add(e, s);
372                 }
373             }
374             syncDeletes();
375             removed.clear();
376             added.clear();
377             updated.clear();
378         }
379         
380         for (Pair<E, String> n : rem) {
381             stopListening(n.first);
382             removeActor(n.first);
383         }
384         
385         for (Pair<E, String> n : add) {
386             addActor(n.first);
387             listen(n.first);
388         }
389         
390         for (E e : mod.getKeys()) {
391             Set<String> ids = mod.getValues(e);
392             if (ids.contains(G3D.URIs.hasPosition) || ids.contains(G3D.URIs.hasOrientation)) {
393                 if (!propagation.contains(e))
394                     propagation.add(e);
395             }
396         }
397
398         if (propagation.size() > 0) {
399             stack.clear();
400             stack.addAll(propagation);
401             propagation.clear();
402             while (!stack.isEmpty()) {
403                 E node = stack.pop();
404                 if (propagation.contains(node))
405                     continue;
406                 propagation.add(node);
407                 for (NodeListener l : node.getListeners()) {
408                     if (l == this) {
409                         //changeTracking = false;
410                         //l.propertyChanged(node, G3D.URIs.hasPosition);
411                         //changeTracking = true;
412                     } else {
413                         l.propertyChanged(node, G3D.URIs.hasWorldPosition);
414                     }
415                 }
416                 if (node instanceof ParentNode) {
417                     stack.addAll(((ParentNode<E>)node).getNodes());
418                 }
419             }
420         }
421         
422 //      synchronized (syncMutex) {
423 //          rem.addAll(removed);
424 //          add.addAll(added);
425 //          //mod.addAll(updated);
426 //          for (E e : updated.getKeys()) {
427 //              for (String s : updated.getValues(e))
428 //                  mod.add(e, s);
429 //          }
430 //          
431 //          removed.clear();
432 //          added.clear();
433 //          updated.clear();
434 //      }
435         
436         for (E e : mod.getKeys()) {
437             Set<String> ids = mod.getValues(e);
438             updateActor(e,ids);
439         }
440         
441         
442         for (Pair<E, String> n : rem) {
443             for (NodeListener l : nodeListeners)
444                 l.nodeRemoved(null, n.first, n.second);
445         }
446         for (Pair<E, String> n : add) {
447             for (NodeListener l : nodeListeners)
448                 l.nodeAdded(n.first.getParent(), n.first, n.second);
449         }
450 //      for (Pair<E, String> n : mod) {
451 //          for (NodeListener l : nodeListeners)
452 //              l.propertyChanged(n.first, n.second);
453 //      }
454         for (E e : mod.getKeys()) {
455             for (NodeListener l : nodeListeners)
456                 for (String s : mod.getValues(e))
457                     l.propertyChanged(e, s);
458         }
459         
460         synchronized (syncMutex) {
461             if (added.isEmpty() && removed.isEmpty() && updated.getKeys().size() == 0)
462                 rangeModified = false;
463         }
464     }
465     
466     @SuppressWarnings("unchecked")
467     private void listen(INode node) {
468         node.addListener(this);
469         if (node instanceof ParentNode<?>) {
470             ParentNode<INode> parentNode = (ParentNode<INode>)node;
471             for (INode n : parentNode.getNodes())
472                 listen(n);
473         }
474     }
475     
476     private void stopListening(INode node) {
477         node.removeListener(this);
478         if (node instanceof ParentNode<?>) {
479             @SuppressWarnings("unchecked")
480             ParentNode<INode> parentNode = (ParentNode<INode>)node;
481             for (INode n : parentNode.getNodes())
482                 stopListening(n);
483         }
484     }
485     
486     @SuppressWarnings("unchecked")
487     @Override
488     public void propertyChanged(INode node, String id) {
489         //receiveUpdate((E)node, id, graphUpdates);
490         receiveUpdate((E)node, id, graphModified.contains(node));
491         
492     }
493     
494     @SuppressWarnings("unchecked")
495     @Override
496     public <T extends INode> void nodeAdded(ParentNode<T> node, INode child,
497             String rel) {
498         if (DEBUG) System.out.println("Node added " + child + " parent " + node);
499         //receiveAdd((E)child, rel ,graphUpdates);
500         receiveAdd((E)child, rel ,graphModified.contains(node));
501         
502     }
503     
504     @SuppressWarnings("unchecked")
505     @Override
506     public <T extends INode> void nodeRemoved(ParentNode<T> node, INode child,
507             String rel) {
508         if (DEBUG) System.out.println("Node removed " + child + " parent " + node);
509         //receiveRemove((E)child, rel, graphUpdates);
510         receiveRemove((E)child, rel, graphModified.contains(node));
511         
512         //FIXME : sometimes removed structural models cause ObjMap to add their children again.
513         //        removing the listener here prevents corruption of visual model, but better fix is needed.
514         stopListening(child);
515     }
516     
517     @Override
518     public void delete() {
519         
520         changeTracking = false;
521         mapping.removeMappingListener(this);
522
523          for (E node : nodes) {
524             node.removeListener(this);
525             removeActor(node);
526             node.cleanup();
527         }
528         nodes.clear();
529     }
530     
531     
532     private List<NodeListener> nodeListeners = new ArrayList<NodeListener>();
533     @Override
534     public void addListener(NodeListener listener) {
535         nodeListeners.add(listener);
536         
537     }
538     
539     @Override
540     public void removeListener(NodeListener listener) {
541         nodeListeners.remove(listener);
542         
543     }
544     
545     public IMapping<Object,E> getMapping() {
546         return mapping;
547     }
548     
549     
550 }