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