Optimize FlyingFrequency, add more structure and comments.

* Use a HashMap for data, as we intend to use the primary thread.
* Move redundant move checking into a method.
* Ignore on-ground, if it is sent too often.
* Log if redundant move checking has to be disabled.
This commit is contained in:
asofold 2015-01-20 18:09:01 +01:00
parent a09917571b
commit f79cda4442
3 changed files with 84 additions and 32 deletions

View File

@ -1,5 +1,6 @@
package fr.neatmonster.nocheatplus.net.protocollib; package fr.neatmonster.nocheatplus.net.protocollib;
import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
@ -13,11 +14,12 @@ import com.comphenix.protocol.events.PacketEvent;
import fr.neatmonster.nocheatplus.NCPAPIProvider; import fr.neatmonster.nocheatplus.NCPAPIProvider;
import fr.neatmonster.nocheatplus.components.JoinLeaveListener; import fr.neatmonster.nocheatplus.components.JoinLeaveListener;
import fr.neatmonster.nocheatplus.logging.Streams;
import fr.neatmonster.nocheatplus.net.NetConfig; import fr.neatmonster.nocheatplus.net.NetConfig;
import fr.neatmonster.nocheatplus.net.NetConfigCache; import fr.neatmonster.nocheatplus.net.NetConfigCache;
import fr.neatmonster.nocheatplus.stats.Counters; import fr.neatmonster.nocheatplus.stats.Counters;
import fr.neatmonster.nocheatplus.time.monotonic.Monotonic;
import fr.neatmonster.nocheatplus.utilities.ActionFrequency; import fr.neatmonster.nocheatplus.utilities.ActionFrequency;
import fr.neatmonster.nocheatplus.utilities.ds.corw.LinkedHashMapCOW;
/** /**
* Prevent extremely fast ticking by just sending packets that don't do anything * Prevent extremely fast ticking by just sending packets that don't do anything
@ -42,12 +44,15 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener
public final float[] floats = new float[2]; // yaw, pitch public final float[] floats = new float[2]; // yaw, pitch
//public final boolean[] booleans = new boolean[3]; // ground, hasPos, hasLook //public final boolean[] booleans = new boolean[3]; // ground, hasPos, hasLook
public boolean onGround = false; public boolean onGround = false;
public long timeOnGround = 0;
public long timeNotOnGround = 0;
public FFData(int seconds) { public FFData(int seconds) {
all = new ActionFrequency(seconds, 1000L); all = new ActionFrequency(seconds, 1000L);
} }
} }
private final Map<String, FFData> freqMap = new LinkedHashMapCOW<String, FFData>(); private final Map<String, FFData> freqMap = new HashMap<String, FFData>();
private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class); private final Counters counters = NCPAPIProvider.getNoCheatPlusAPI().getGenericInstance(Counters.class);
private final int idSilent = counters.registerKey("packet.flying.silentcancel"); private final int idSilent = counters.registerKey("packet.flying.silentcancel");
private final int idRedundant = counters.registerKey("packet.flying.silentcancel.redundant"); private final int idRedundant = counters.registerKey("packet.flying.silentcancel.redundant");
@ -73,7 +78,7 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener
freqMap.remove(player.getName()); freqMap.remove(player.getName());
} }
private FFData getFreq(final String name, final NetConfig cc) { private FFData getData(final String name, final NetConfig cc) {
final FFData freq = this.freqMap.get(name); final FFData freq = this.freqMap.get(name);
if (freq != null) { if (freq != null) {
return freq; return freq;
@ -100,11 +105,11 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener
return; return;
} }
final FFData freq = getFreq(player.getName(), cc); final FFData data = getData(player.getName(), cc);
final long t = System.currentTimeMillis(); final long t = System.currentTimeMillis();
// Counting all packets. // Counting all packets.
freq.all.add(t, 1f); data.all.add(t, 1f);
final float allScore = freq.all.score(1f); final float allScore = data.all.score(1f);
if (allScore > cc.flyingFrequencyMaxPackets) { if (allScore > cc.flyingFrequencyMaxPackets) {
counters.add(idSilent, 1); // Until it is sure if we can get these async. counters.add(idSilent, 1); // Until it is sure if we can get these async.
event.setCancelled(true); event.setCancelled(true);
@ -112,58 +117,91 @@ public class FlyingFrequency extends PacketAdapter implements JoinLeaveListener
} }
// Cancel redundant packets, when frequency is high anyway. // Cancel redundant packets, when frequency is high anyway.
if (!cancelRedundant || !cc.flyingFrequencyCancelRedundant) { if (cancelRedundant && cc.flyingFrequencyCancelRedundant && checkRedundantPackets(event, allScore, data, cc)) {
return; event.setCancelled(true);
} }
// TODO: Consider to detect if a moving event would fire (...).
}
private boolean checkRedundantPackets(final PacketEvent event, final float allScore, final FFData data, final NetConfig cc) {
// TODO: Check vs. MovingData last-to.
// TODO: Consider quick return conditions.
// TODO: Debug logging (better with integration into DataManager).
boolean redundant = true; boolean redundant = true;
final PacketContainer packet = event.getPacket(); final PacketContainer packet = event.getPacket();
final List<Boolean> booleans = packet.getBooleans().getValues(); final List<Boolean> booleans = packet.getBooleans().getValues();
if (booleans.size() != FFData.numBooleans) { if (booleans.size() != FFData.numBooleans) {
cancelRedundant = false; return packetMismatch();
return;
} }
final boolean onGround = booleans.get(FFData.indexOnGround).booleanValue(); final boolean onGround = booleans.get(FFData.indexOnGround).booleanValue();
if (onGround != freq.onGround) { final boolean hasPos = booleans.get(FFData.indexhasPos);
redundant = false; final boolean hasLook = booleans.get(FFData.indexhasLook);
}
freq.onGround = onGround; if (hasPos) {
// TODO: Consider to verify on ground somehow.
if (booleans.get(FFData.indexhasPos)) {
final List<Double> doubles = packet.getDoubles().getValues(); final List<Double> doubles = packet.getDoubles().getValues();
if (doubles.size() != freq.doubles.length) { if (doubles.size() != data.doubles.length) {
cancelRedundant = false; return packetMismatch();
return;
} }
for (int i = 0; i < freq.doubles.length; i++) { for (int i = 0; i < data.doubles.length; i++) {
final double val = doubles.get(i).doubleValue(); final double val = doubles.get(i).doubleValue();
if (val != freq.doubles[i]) { if (val != data.doubles[i]) {
redundant = false; redundant = false;
freq.doubles[i] = val; data.doubles[i] = val;
} }
} }
} }
if (booleans.get(FFData.indexhasLook)) {
if (hasLook) {
final List<Float> floats = packet.getFloat().getValues(); final List<Float> floats = packet.getFloat().getValues();
if (floats.size() != freq.floats.length) { if (floats.size() != data.floats.length) {
cancelRedundant = false; return packetMismatch();
return;
} }
for (int i = 0; i < freq.floats.length; i++) { for (int i = 0; i < data.floats.length; i++) {
final float val = floats.get(i).floatValue(); final float val = floats.get(i).floatValue();
if (val != freq.floats[i]) { if (val != data.floats[i]) {
redundant = false; redundant = false;
freq.floats[i] = val; data.floats[i] = val;
} }
} }
} }
// TODO: Consider to verify on ground somehow (could tell MovingData the state).
if (onGround != data.onGround) {
// Regard as not redundant only if sending the same state happened at least a second ago.
final long time = Monotonic.millis();
final long lastTime;
if (onGround) {
lastTime = data.timeOnGround;
data.timeOnGround = time;
} else {
lastTime = data.timeNotOnGround;
data.timeNotOnGround = time;
}
if (time - lastTime < 1000) {
redundant = false;
}
}
data.onGround = onGround;
if (redundant) { if (redundant) {
// TODO: Could check first bucket or even just 50 ms to last packet. // TODO: Could check first bucket or even just 50 ms to last packet.
if (allScore / cc.flyingFrequencySeconds > 20f) { if (allScore / cc.flyingFrequencySeconds > 20f) {
counters.add(idRedundant, 1); counters.add(idRedundant, 1);
event.setCancelled(true); return true;
} }
} }
return false;
}
/**
* Log warning to console, halt checking for redundant packets.
* @return Always returns false;
*/
private boolean packetMismatch() {
cancelRedundant = false;
NCPAPIProvider.getNoCheatPlusAPI().getLogManager().warning(Streams.STATUS, "[NoCheatPlus] Data mismatch: disable cancelling of redundant packets.");
return false;
} }
} }

View File

@ -10,10 +10,19 @@ import org.bukkit.World;
*/ */
public abstract class WorldConfigCache<C> extends ConfigCache<String, C> { public abstract class WorldConfigCache<C> extends ConfigCache<String, C> {
/**
*
* @param cow If true, copy-on-write is used (thread-safe), otherwise an ordinary HashMap.
*/
public WorldConfigCache(boolean cow) { public WorldConfigCache(boolean cow) {
this(cow, 10); this(cow, 10);
} }
/**
*
* @param cow If true, copy-on-write is used (thread-safe), otherwise an ordinary HashMap.
* @param initialCapacity
*/
public WorldConfigCache(boolean cow, int initialCapacity) { public WorldConfigCache(boolean cow, int initialCapacity) {
super(cow, initialCapacity); super(cow, initialCapacity);
} }

View File

@ -3,6 +3,11 @@ package fr.neatmonster.nocheatplus.net;
import fr.neatmonster.nocheatplus.config.ConfigFile; import fr.neatmonster.nocheatplus.config.ConfigFile;
import fr.neatmonster.nocheatplus.config.WorldConfigCache; import fr.neatmonster.nocheatplus.config.WorldConfigCache;
/**
* Copy-on-write per-world configuration cache.
* @author web4web1
*
*/
public class NetConfigCache extends WorldConfigCache<NetConfig> { public class NetConfigCache extends WorldConfigCache<NetConfig> {
public NetConfigCache() { public NetConfigCache() {