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