Ensure ActionFRequency does reset if time ran backwards.

This commit is contained in:
asofold 2014-07-31 10:02:49 +02:00
parent 2f13529a29
commit 24120f306a

View File

@ -8,224 +8,237 @@ package fr.neatmonster.nocheatplus.utilities;
*/ */
public class ActionFrequency { public class ActionFrequency {
/** Reference time for filling in. */ /** Reference time for filling in. */
private long time = 0; private long time = 0;
private long lastUpdate = 0;
/** /**
* Buckets to fill weights in, each represents an interval of durBucket duration, * Buckets to fill weights in, each represents an interval of durBucket duration,
* index 0 is the latest, highest index is the oldest. * index 0 is the latest, highest index is the oldest.
* Weights will get filled into the next buckets with time passed. * Weights will get filled into the next buckets with time passed.
*/ */
private final float[] buckets; private final float[] buckets;
/** Duration in milliseconds that oe bucket covers. */ /** Duration in milliseconds that oe bucket covers. */
private final long durBucket; private final long durBucket;
public ActionFrequency(final int nBuckets, final long durBucket){ public ActionFrequency(final int nBuckets, final long durBucket) {
this.buckets = new float[nBuckets]; this.buckets = new float[nBuckets];
this.durBucket = durBucket; this.durBucket = durBucket;
} }
/** /**
* Update and add. * Update and add.
* @param now * @param now
* @param amount * @param amount
*/ */
public final void add(final long now, final float amount){ public final void add(final long now, final float amount) {
update(now); update(now);
buckets[0] += amount; buckets[0] += amount;
} }
/** /**
* Unchecked addition of amount to the first bucket. * Unchecked addition of amount to the first bucket.
* @param amount * @param amount
*/ */
public final void add(final float amount){ public final void add(final float amount) {
buckets[0] += amount; buckets[0] += amount;
} }
/** /**
* Update without adding, also updates time. * Update without adding, also updates time. Detects time running backwards.
* @param now * @param now
*/ */
public final void update(final long now) { public final void update(final long now) {
final long diff = now - time; final long diff = now - time;
if (diff < durBucket){ if (now < lastUpdate || diff >= durBucket * buckets.length) {
// No update (first bucket). // Clear (beyond range).
return; clear(now);
} return;
else if (diff >= durBucket * buckets.length || diff < 0){ }
// Clear (beyond range). else if (diff < durBucket) {
clear(now); // No update (first bucket).
return; return;
} }
final int shift = (int) ((float) diff / (float) durBucket); final int shift = (int) ((float) diff / (float) durBucket);
// Update buckets. // Update buckets.
for (int i = 0; i < buckets.length - shift; i++){ for (int i = 0; i < buckets.length - shift; i++) {
buckets[buckets.length - (i + 1)] = buckets[buckets.length - (i + 1 + shift)]; buckets[buckets.length - (i + 1)] = buckets[buckets.length - (i + 1 + shift)];
} }
for (int i = 0; i < shift; i++){ for (int i = 0; i < shift; i++) {
buckets[i] = 0; buckets[i] = 0;
} }
// Set time according to bucket duration (!). // Set time according to bucket duration (!).
time += durBucket * shift; time += durBucket * shift;
} lastUpdate = now;
}
public final void clear(final long now) { public final void clear(final long now) {
for (int i = 0; i < buckets.length; i++){ for (int i = 0; i < buckets.length; i++) {
buckets[i] = 0f; buckets[i] = 0f;
} }
time = now; time = lastUpdate = now;
} }
/** /**
* @deprecated Use instead: score(float). * @deprecated Use instead: score(float).
* @param factor * @param factor
* @return * @return
*/ */
public final float getScore(final float factor){ public final float getScore(final float factor) {
return score(factor); return score(factor);
} }
/** /**
* @deprecated Use instead: score(float). * @deprecated Use instead: score(float).
* @param factor * @param factor
* @return * @return
*/ */
public final float getScore(final int bucket){ public final float getScore(final int bucket) {
return bucketScore(bucket); return bucketScore(bucket);
} }
/** /**
* Get a weighted sum score, weight for bucket i: w(i) = factor^i. * Get a weighted sum score, weight for bucket i: w(i) = factor^i.
* @param factor * @param factor
* @return * @return
*/ */
public final float score(final float factor){ public final float score(final float factor) {
return sliceScore(0, buckets.length, factor); return sliceScore(0, buckets.length, factor);
} }
/** /**
* Get score of a certain bucket. At own risk. * Get score of a certain bucket. At own risk.
* @param bucket * @param bucket
* @return * @return
*/ */
public final float bucketScore(final int bucket){ public final float bucketScore(final int bucket) {
return buckets[bucket]; return buckets[bucket];
} }
/** /**
* Get score of first end buckets, with factor. * Get score of first end buckets, with factor.
* @param end Number of buckets including start. The end is not included. * @param end Number of buckets including start. The end is not included.
* @param factor * @param factor
* @return * @return
*/ */
public final float leadingScore(final int end, float factor){ public final float leadingScore(final int end, float factor) {
return sliceScore(0, end, factor); return sliceScore(0, end, factor);
} }
/** /**
* Get score from start on, with factor. * Get score from start on, with factor.
* @param start This is included. * @param start This is included.
* @param factor * @param factor
* @return * @return
*/ */
public final float trailingScore(final int start, float factor){ public final float trailingScore(final int start, float factor) {
return sliceScore(start, buckets.length, factor); return sliceScore(start, buckets.length, factor);
} }
/** /**
* Get score from start on, until before end, with factor. * Get score from start on, until before end, with factor.
* @param start This is included. * @param start This is included.
* @param end This is not included. * @param end This is not included.
* @param factor * @param factor
* @return * @return
*/ */
public final float sliceScore(final int start, final int end, float factor){ public final float sliceScore(final int start, final int end, float factor) {
float score = buckets[start]; float score = buckets[start];
float cf = factor; float cf = factor;
for (int i = start + 1; i < end; i++){ for (int i = start + 1; i < end; i++) {
score += buckets[i] * cf; score += buckets[i] * cf;
cf *= factor; cf *= factor;
} }
return score; return score;
} }
/** /**
* Set the value for a buckt. * Set the value for a buckt.
* @param n * @param n
* @param value * @param value
*/ */
public final void setBucket(final int n, final float value){ public final void setBucket(final int n, final float value) {
buckets[n] = value; buckets[n] = value;
} }
/** /**
* Set the reference time. * Set the reference time.
* @param time * @param time
*/ */
public final void setTime(final long time){ public final void setTime(final long time) {
this.time = time; this.time = time;
} this.lastUpdate = time;
}
/** /**
* Get reference time. * Get reference time.
* @return * @return
*/ */
public final long lastAccess(){ public final long lastAccess() { // TODO: Should rename this.
return time; return time;
} }
/** /**
* Get the number of buckets. *
* @return * @return
*/ */
public final int numberOfBuckets(){ public final long lastUpdate() {
return buckets.length; return lastUpdate;
} }
/** /**
* Get the duration of a bucket in milliseconds. * Get the number of buckets.
* @return * @return
*/ */
public final long bucketDuration(){ public final int numberOfBuckets() {
return durBucket; return buckets.length;
} }
/** /**
* Serialize to a String line. * Get the duration of a bucket in milliseconds.
* @return * @return
*/ */
public final String toLine(){ public final long bucketDuration() {
final StringBuilder buffer = new StringBuilder(50); return durBucket;
buffer.append(buckets.length + ","+durBucket+","+time); }
for (int i = 0; i < buckets.length; i++){
buffer.append("," + buckets[i]);
}
return buffer.toString();
}
/** /**
* Deserialize from a string. * Serialize to a String line.
* @param line * @return
* @return */
*/ public final String toLine() {
public static ActionFrequency fromLine(final String line){ // TODO: Backwards-compatible lastUpdate ?
String[] split = line.split(","); final StringBuilder buffer = new StringBuilder(50);
if (split.length < 3) throw new RuntimeException("Bad argument length."); // TODO buffer.append(buckets.length + ","+durBucket+","+time);
final int n = Integer.parseInt(split[0]); for (int i = 0; i < buckets.length; i++) {
final long durBucket = Long.parseLong(split[1]); buffer.append("," + buckets[i]);
final long time = Long.parseLong(split[2]); }
final float[] buckets = new float[split.length -3]; return buffer.toString();
if (split.length - 3 != buckets.length) throw new RuntimeException("Bad argument length."); // TODO }
for (int i = 3; i < split.length; i ++){
buckets[i - 3] = Float.parseFloat(split[i]); /**
} * Deserialize from a string.
ActionFrequency freq = new ActionFrequency(n, durBucket); * @param line
freq.setTime(time); * @return
for (int i = 0; i < buckets.length; i ++){ */
freq.setBucket(i, buckets[i]); public static ActionFrequency fromLine(final String line) {
} // TODO: Backwards-compatible lastUpdate ?
return freq; String[] split = line.split(",");
} if (split.length < 3) throw new RuntimeException("Bad argument length."); // TODO
final int n = Integer.parseInt(split[0]);
final long durBucket = Long.parseLong(split[1]);
final long time = Long.parseLong(split[2]);
final float[] buckets = new float[split.length -3];
if (split.length - 3 != buckets.length) throw new RuntimeException("Bad argument length."); // TODO
for (int i = 3; i < split.length; i ++) {
buckets[i - 3] = Float.parseFloat(split[i]);
}
ActionFrequency freq = new ActionFrequency(n, durBucket);
freq.setTime(time);
for (int i = 0; i < buckets.length; i ++) {
freq.setBucket(i, buckets[i]);
}
return freq;
}
} }