/* * Aphelion * Copyright (c) 2013 Joris van der Wel * * This file is part of Aphelion * * Aphelion is free software: you can redistribute it and/or modify * it under the terms of the GNU Affero General Public License as published by * the Free Software Foundation, version 3 of the License. * * Aphelion is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU Affero General Public License * along with Aphelion. If not, see <http://www.gnu.org/licenses/>. * * In addition, the following supplemental terms apply, based on section 7 of * the GNU Affero General Public License (version 3): * a) Preservation of all legal notices and author attributions * b) Prohibition of misrepresentation of the origin of this material, and * modified versions are required to be marked in reasonable ways as * different from the original version (for example by appending a copyright notice). * * Linking this library statically or dynamically with other modules is making a * combined work based on this library. Thus, the terms and conditions of the * GNU Affero General Public License cover the whole combination. * * As a special exception, the copyright holders of this library give you * permission to link this library with independent modules to produce an * executable, regardless of the license terms of these independent modules, * and to copy and distribute the resulting executable under terms of your * choice, provided that you also meet, for each linked independent module, * the terms and conditions of the license of that module. An independent * module is a module which is not derived from or based on this library. */ package aphelion.shared.swissarmyknife; /** * * @author Joris */ public class RollingHistorySerialInteger { public final int HISTORY_LENGTH; public final int SETTERS; private int history_index = 0; private long history_tick = 0; private boolean dirty; private long dirtySince_tick; // only valid if dirty = true private int oldestValue; // The value that is right before the oldest value we can look up in values[] private final int[] values; private final int[][] delta; // delta increase since tick - 1 private final byte[][] absolute; // if set to 1, the "tick - 1" value is 0 in any "tick >=" calculations. // if set to 2, ignore all other setters and min/max private final int[] min; private final int[] max; public RollingHistorySerialInteger(long initial_tick, int history_length, int setters) { this.HISTORY_LENGTH = history_length; this.history_tick = initial_tick; this.SETTERS = setters; values = new int[HISTORY_LENGTH]; delta = new int[SETTERS][]; absolute = new byte[SETTERS][]; for (int i = 0; i < SETTERS; ++i) { delta[i] = new int[HISTORY_LENGTH]; absolute[i] = new byte[HISTORY_LENGTH]; } min = new int[HISTORY_LENGTH]; max = new int[HISTORY_LENGTH]; for (int i = 0; i < HISTORY_LENGTH; ++i) { min[i] = Integer.MIN_VALUE; max[i] = Integer.MAX_VALUE; } } private int tickToIndex(long tick, boolean create) { while (tick > history_tick) { if (!create) { return -1; } if (dirty && dirtySince_tick == history_tick - HISTORY_LENGTH + 1) { // Really have to update now updateDirty(); } ++history_index; ++history_tick; if (history_index == HISTORY_LENGTH) { history_index = 0; } int prev_index = history_index - 1; if (prev_index < 0) { assert prev_index == -1; prev_index = HISTORY_LENGTH-1; } // About to discard the oldest value, // store it. // (if we have less than HISTORY_LENGTH values, oldestValue will become 0) oldestValue = values[history_index]; values[history_index] = values[prev_index]; for (int s = 0; s < SETTERS; ++s) { delta[s][history_index] = 0; absolute[s][history_index] = 0; } min[history_index] = min[prev_index]; max[history_index] = max[prev_index]; } long tick_diff = history_tick - tick; if (tick_diff >= HISTORY_LENGTH) { // My history does not go back that far return -1; } int index = history_index - (int) tick_diff; if (index < 0) { index += HISTORY_LENGTH; } return index; } /** Set a tick to an relative value by adding up previous ticks. */ public void setRelativeValue(int setterID, long tick, int deltaValue) { int index = tickToIndex(tick, true); if (index >= 0) { if (delta[setterID][index] != deltaValue || absolute[setterID][index] != 0) { delta[setterID][index] = deltaValue; absolute[setterID][index] = 0; markDirty(tick); } } } /** Set a tick to an absolute value by ignoring previous ticks. */ public void setAbsoluteValue(int setterID, long tick, int absoluteValue) { int index = tickToIndex(tick, true); if (index >= 0) { if (delta[setterID][index] != absoluteValue || absolute[setterID][index] != 1) { delta[setterID][index] = absoluteValue; absolute[setterID][index] = 1; markDirty(tick); } } } /** Set a tick to an absolute value by ignoring previous ticks and ignore all other setters. * Minimum and maximum is also ignored. */ public void setAbsoluteOverrideValue(int setterID, long tick, int absoluteValue) { int index = tickToIndex(tick, true); if (index >= 0) { if (delta[setterID][index] != absoluteValue || absolute[setterID][index] != 2) { delta[setterID][index] = absoluteValue; absolute[setterID][index] = 2; markDirty(tick); } } } public int getSetterValue(int setterID, long tick) { int index = tickToIndex(tick, true); if (index >= 0) { return delta[setterID][index]; } return 0; } public void addRelativeValue(int setterID, long tick, int deltaValue) { if (deltaValue == 0) { return; } int index = tickToIndex(tick, true); if (index >= 0 && absolute[setterID][index] != 2) // do not add if setAbsoluteOverrideValue was used { delta[setterID][index] += deltaValue; markDirty(tick); } } public void addAbsoluteValue(int setterID, long tick, int absoluteValue) { int index = tickToIndex(tick, true); if (index >= 0 && absolute[setterID][index] != 2) // do not add if setAbsoluteOverrideValue was used { delta[setterID][index] += absoluteValue; if (absolute[setterID][index] == 0) { absolute[setterID][index] = 1; } markDirty(tick); } } public void setMinimum(long tick, int minimum) { int index = tickToIndex(tick, true); if (index >= 0) { if (min[index] != minimum) { min[index] = minimum; markDirty(tick); } } } public void setMaximum(long tick, int maximum) { int index = tickToIndex(tick, true); if (index >= 0) { if (max[index] != maximum) { max[index] = maximum; markDirty(tick); } } } private void markDirty(long tick) { if (!dirty || tick < dirtySince_tick) { dirty = true; dirtySince_tick = tick; } } private int getOldestIndex() { int oldestIndex = this.history_index - (HISTORY_LENGTH - 1); if (oldestIndex < 0) { oldestIndex = HISTORY_LENGTH + oldestIndex; } return oldestIndex; } private void updateDirty() { long value; if (!dirty) { return; } if (this.dirtySince_tick > this.history_tick) { return; } int index = this.tickToIndex(this.dirtySince_tick, false); assert index >= 0; // if tick is out of range, we have not updated enough int oldestIndex = getOldestIndex(); if (index == oldestIndex) { value = this.oldestValue; } else { if (index == 0) { value = this.values[HISTORY_LENGTH-1]; } else { value = this.values[index-1]; } } while (true) { boolean hasAbsolute = false; boolean hasAbsoluteOverride = false; long localValue = 0; for (int s = 0; s < SETTERS; ++s) { if (absolute[s][index] > 0) { hasAbsolute = true; } if (absolute[s][index] == 2) { localValue = this.delta[s][index]; hasAbsoluteOverride = true; break; } localValue += this.delta[s][index]; } value = (hasAbsolute ? 0 : value) + localValue; if (!hasAbsoluteOverride) { value = SwissArmyKnife.clip(value, this.min[index], this.max[index]); } this.values[index] = (int) value; if (index == this.history_index) { break; } ++index; if (index == HISTORY_LENGTH) { index = 0; } } this.dirty = false; } /** Get the total value for a tick. Relative values of previous ticks are added up. */ public int get(long tick) { int index = tickToIndex(tick > this.history_tick ? this.history_tick : tick, false); if (index >= 0) { updateDirty(); return values[index]; } return 0; } public boolean hasValueFor(long tick) { return tickToIndex(tick, false) >= 0; } public long getMostRecentTick() { return history_tick; } public void set(RollingHistorySerialInteger other) { if (other.HISTORY_LENGTH > this.HISTORY_LENGTH) { throw new IllegalArgumentException(); } if (other.SETTERS != this.SETTERS) { throw new IllegalArgumentException(); } history_tick = other.history_tick; dirty = other.dirty; dirtySince_tick = other.dirtySince_tick; oldestValue = other.oldestValue; this.history_index = this.HISTORY_LENGTH - 1; int myIndex = this.history_index; int otherIndex = other.history_index; boolean moreData = true; while (myIndex >= 0) { if (moreData) { this.values[myIndex] = other.values[otherIndex]; this.min[myIndex] = other.min[otherIndex]; this.max[myIndex] = other.max[otherIndex]; for (int s = 0; s < SETTERS; ++s) { this.delta[s][myIndex] = other.delta[s][otherIndex]; this.absolute[s][myIndex] = other.absolute[s][otherIndex]; } --otherIndex; if (otherIndex == -1) { otherIndex = other.HISTORY_LENGTH-1; } assert otherIndex >= 0; if (otherIndex == other.history_index) { moreData = false; } } else { this.values[myIndex] = other.oldestValue; this.min[myIndex] = Integer.MIN_VALUE; this.max[myIndex] = Integer.MAX_VALUE; for (int s = 0; s < SETTERS; ++s) { this.delta[s][myIndex] = 0; this.absolute[s][myIndex] = 0; } } --myIndex; } } }