Tested and fixed utility for truncating history data. 80/1280/2
authorTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Sun, 3 Dec 2017 20:07:15 +0000 (22:07 +0200)
committerTuukka Lehtonen <tuukka.lehtonen@semantum.fi>
Sun, 3 Dec 2017 20:08:36 +0000 (22:08 +0200)
HistoryUtil.truncateHistory now actually works as intended. Care must be
taken not to interfere with the history data while it is being
truncated. This must be ensured externally.

A reference counter (AtomicInteger) was also added to ChartData to be
able to see how many users the ChartData has and to wait until it no
longer has any users. This is required to wait until all chart editors
have detached themselves from the history data.

refs #7622

Change-Id: I35863f5258da2cae193b3086500b5514c4699518

bundles/org.simantics.charts/src/org/simantics/charts/editor/ChartData.java
bundles/org.simantics.charts/src/org/simantics/charts/editor/TimeSeriesEditor.java
bundles/org.simantics.history/src/org/simantics/history/impl/CollectorState.java
bundles/org.simantics.simulation/src/org/simantics/simulation/history/HistoryUtil.java

index b44556b633713f976ce7ec437f715d86919dd287..e85c080370e0b2d774aa0b9c174b8424fae84c13 100644 (file)
@@ -11,6 +11,8 @@
  *******************************************************************************/
 package org.simantics.charts.editor;
 
+import java.util.concurrent.atomic.AtomicInteger;
+
 import org.simantics.databoard.annotations.Optional;
 import org.simantics.db.Resource;
 import org.simantics.history.Collector;
@@ -58,6 +60,13 @@ public final class ChartData {
     @Optional
     public Collector      collector;
 
+    /**
+     * This (shared instance) is used to track the amount of users of this ChartData
+     * instance. The same instance may end up in many ChartData instances through
+     * {@link #readFrom(ChartData)}.
+     */
+    public AtomicInteger  refCount = new AtomicInteger();
+
     public ChartData(Resource model, Resource run, IExperiment experiment, Datasource datasource, HistoryManager history, Collector collector) {
         this.model = model;
         this.run = run;
@@ -75,6 +84,7 @@ public final class ChartData {
                this.datasource = null;
                this.history = null;
                this.collector = null;
+               this.refCount = null;
        } else {
                this.model = other.model;
                this.run = other.run;
@@ -82,6 +92,7 @@ public final class ChartData {
                this.datasource = other.datasource;
                this.history = other.history;
                this.collector = other.collector;
+               this.refCount = other.refCount;
        }
     }
 
@@ -99,4 +110,38 @@ public final class ChartData {
        }
     }
 
+    public int reference() {
+        AtomicInteger i = refCount;
+        if (i == null)
+            return 0;
+        int result = i.incrementAndGet();
+        //System.out.println(this + ": reference: " + (result-1) + " -> " + result + " (" + System.identityHashCode(refCount) + ")");
+        return result;
+    }
+
+    public int dereference() {
+        AtomicInteger i = refCount;
+        if (i == null)
+            return 0;
+        int result = i.decrementAndGet();
+        //System.out.println(this + ": dereference: " + (result+1) + " -> " + result + " (" + System.identityHashCode(refCount) + ")");
+        if (result <= 0) {
+            synchronized (i) {
+                i.notifyAll();
+            }
+        }
+        return result;
+    }
+
+    public void waitUntilNotReferenced() throws InterruptedException {
+        AtomicInteger i = refCount;
+        if (i == null)
+            return;
+        synchronized (i) {
+            while (i.get() > 0) {
+                i.wait();
+            }
+        }
+    }
+
 }
index 4f633fbe6924c5478277604500b9ada221b2334d..cadb5bd4b372e5841479497d672cd1fde938511e 100644 (file)
@@ -586,6 +586,7 @@ public class TimeSeriesEditor extends ResourceEditorPart {
         // Track data source and preinitialize chartData
         project.addHintListener(chartDataListener);
         chartData.readFrom( (ChartData) project.getHint( chartDataKey ) );
+        chartData.reference();
 
         if (chartData.run != null) {
             milestoneListener = new MilestoneSpecListener();
@@ -748,6 +749,7 @@ public class TimeSeriesEditor extends ResourceEditorPart {
                 chartData.datasource.removeListener( stepListener );
             if (chartData.experiment!=null)
                 chartData.experiment.removeListener( experimentStateListener );
+            chartData.dereference();
             chartData.readFrom( null );
         }
 
@@ -840,7 +842,9 @@ public class TimeSeriesEditor extends ResourceEditorPart {
         }
 
         if (doLayout) trendNode.layout();
+        this.chartData.dereference();
         this.chartData.readFrom( data );
+        this.chartData.reference();
         tp.setDirty();
         
         if (!ObjectUtils.objectEquals(oldExperimentResource, newExperimentResource)) {
@@ -989,18 +993,18 @@ public class TimeSeriesEditor extends ResourceEditorPart {
         tp.setDirty();
     }
 
-    @SuppressWarnings("rawtypes")
+    @SuppressWarnings("unchecked")
     @Override
-    public Object getAdapter(Class adapter) {
+    public <T> T getAdapter(Class<T> adapter) {
         if (adapter == INode.class) {
             ICanvasContext ctx = cvsCtx;
             if (ctx != null)
-                return ctx.getSceneGraph();
+                return (T) ctx.getSceneGraph();
         }
         if (adapter == IPropertyPage.class)
-            return new StandardPropertyPage(getSite(), getPropertyPageContexts());
+            return (T) new StandardPropertyPage(getSite(), getPropertyPageContexts());
         if (adapter == ICanvasContext.class)
-            return cvsCtx;
+            return (T) cvsCtx;
         return super.getAdapter(adapter);
     }
 
index bcecf75a506cd13e9a618b158dd84fc7fd26573a..112cc0206606dea87f58277ca1376728168fd251 100644 (file)
@@ -50,7 +50,7 @@ public class CollectorState extends Bean {
 
        public static class VariableState {
                
-               /** The actual value, type is Void is no value */
+               /** The actual value, type is Void if no value */
                public MutableVariant value;
                
                /** Set to true if is valid value */
index 53a76d1360d783011320f451eb86838aa170d1bb..27f2bf895bff382b0ae05cf251cd160425249739 100644 (file)
@@ -52,14 +52,15 @@ import org.simantics.db.exception.DatabaseException;
 import org.simantics.db.exception.ServiceNotFoundException;
 import org.simantics.db.request.Read;
 import org.simantics.fastlz.FastLZ;
-import org.simantics.history.Collector;
 import org.simantics.history.HistoryException;
 import org.simantics.history.HistoryManager;
 import org.simantics.history.ItemManager;
+import org.simantics.history.impl.CollectorImpl;
 import org.simantics.history.impl.CollectorState;
 import org.simantics.history.impl.CollectorState.VariableState;
 import org.simantics.history.util.Stream;
 import org.simantics.history.util.ValueBand;
+import org.simantics.history.util.WeightedMedian;
 import org.simantics.layer0.Layer0;
 import org.simantics.simulation.Activator;
 import org.simantics.simulation.ontology.HistoryResource;
@@ -949,17 +950,16 @@ public class HistoryUtil {
                return 0L;
        }
 
-       public static void truncateHistory(double toBeforeTime, HistoryManager history, Collector collector) throws AccessorException, BindingException, HistoryException {
+       public static CollectorState truncateHistory(double toBeforeTime, HistoryManager history, CollectorState state) throws AccessorException, BindingException, HistoryException {
                Double t = toBeforeTime;
                Binding timeBinding = null;
-               CollectorState state = collector != null ? (CollectorState) collector.getState() : null;
 
                Bean[] items = history.getItems();
                //System.out.println("truncating all samples after t=" + toBeforeTime + " for " + items.length + " history items");
 
                for (Bean item : items) {
                        String id = (String) item.getField("id");
-                       StreamAccessor sa = history.openStream(id, "w");
+                       StreamAccessor sa = history.openStream(id, "rw");
                        try {
                                Stream s = new Stream(sa);
                                timeBinding = s.timeBinding;
@@ -972,7 +972,8 @@ public class HistoryUtil {
 
                                        if (state != null) {
                                                Object prevTime  = newSize > 0 ? s.getItemTime(s.timeBinding, newSize - 1) : null;
-                                               Object prevValue = newSize > 0 ? sa.get(newSize - 1, s.valueBinding) : null;
+                                               Bean prevSample = newSize > 0 ? (Bean) sa.get(newSize - 1, s.sampleBinding) : null;
+                                               Object prevValue = prevSample != null ? prevSample.getField(s.valueIndex) : null;
                                                boolean isNan = isNaN(prevValue);
 
                                                VariableState vs = state.values.get(id);
@@ -984,18 +985,18 @@ public class HistoryUtil {
 
                                                CollectorState.Item is = state.itemStates.get(id);
                                                if (is != null) {
-                                                       is.firstTime = Double.NaN;
-                                                       is.firstValue = null;
-                                                       is.currentTime = toTime(prevTime);
-                                                       is.currentValue = prevValue != null ? new MutableVariant(s.valueBinding, prevValue) : null;
-                                                       is.isNaN = isNaN(is.currentValue);
-                                                       is.isValid = is.currentValue != null;
+                                                       is.firstTime = toTime(prevTime);
+                                                       is.firstValue = toValue(s.valueBinding, prevValue);
+                                                       is.currentTime = is.firstTime;
+                                                       is.currentValue = toValue(s.valueBinding, prevValue);
+                                                       is.isNaN = isNan;
+                                                       is.isValid = prevValue != null;
                                                        is.sum = Double.NaN;
                                                        is.count = 0;
                                                        is.ooDeadband = false;
                                                        is.firstDisabledTime = Double.NaN;
                                                        is.lastDisabledTime = Double.NaN;
-                                                       is.median = null;
+                                                       is.median = new WeightedMedian( CollectorImpl.MEDIAN_LIMIT );
                                                }
                                        }
                                }
@@ -1007,14 +1008,19 @@ public class HistoryUtil {
                if (timeBinding != null && state != null) {
                        state.time.setValue(timeBinding, t);
                        state.dT = 1.0;
-                       collector.setState(state);
                }
+
+               return state;
        }
 
        private static double toTime(Object time) {
                return time instanceof Number ? ((Number) time).doubleValue() : Double.NaN;
        }
 
+       private static MutableVariant toValue(Binding valueBinding, Object value) {
+               return new MutableVariant(valueBinding, value != null ? value : valueBinding.createDefaultUnchecked());
+       }
+
        private static boolean isNaN(Object value) {
                return value instanceof Number ? Double.isNaN(((Number) value).doubleValue()) : false;
        }