RAW CODING: completely asynchronous chat (executeActions is synched

into main thread).
This commit is contained in:
asofold 2012-08-08 20:07:13 +02:00
parent 7f9d2d2c11
commit 889930fd9b
6 changed files with 177 additions and 52 deletions

View File

@ -12,6 +12,7 @@ import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.plugin.PluginDescriptionFile;
import org.bukkit.plugin.java.JavaPlugin;
import fr.neatmonster.nocheatplus.checks.ExecuteActionsEvent;
import fr.neatmonster.nocheatplus.checks.Workarounds;
import fr.neatmonster.nocheatplus.checks.blockbreak.BlockBreakListener;
import fr.neatmonster.nocheatplus.checks.blockinteract.BlockInteractListener;
@ -188,4 +189,9 @@ public class NoCheatPlus extends JavaPlugin implements Listener {
player.sendMessage(message);
}
@EventHandler(priority=EventPriority.LOWEST)
final void onExecuteActions(final ExecuteActionsEvent event){
}
}

View File

@ -0,0 +1,52 @@
package fr.neatmonster.nocheatplus.checks;
import org.bukkit.entity.Player;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
/**
* This event is to be fired to execute actions in the main thread.
* @author mc_dev
*
*/
public class ExecuteActionsEvent extends Event {
private static final HandlerList handlers = new HandlerList();
private final Check check;
final Player player;
/**
* If the actions have been executed already.
*/
private boolean actionsExecuted = false;
private boolean cancel = false;
public ExecuteActionsEvent(final Check check, final Player player){
this.check = check;
this.player = player;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
/**
* Must have :_) ...
* @return
*/
public static HandlerList getHandlerList() {
return handlers;
}
public void executeActions(){
if (actionsExecuted) return;
cancel = check.executeActions(player);
actionsExecuted = true;
}
public boolean getCancel(){
return cancel;
}
}

View File

@ -34,7 +34,9 @@ public class ChatConfig {
* Clear all the configurations.
*/
public static void clear() {
worldsMap.clear();
synchronized (worldsMap) {
worldsMap.clear();
}
}
/**
@ -45,10 +47,12 @@ public class ChatConfig {
* @return the configuration
*/
public static ChatConfig getConfig(final Player player) {
if (!worldsMap.containsKey(player.getWorld().getName()))
worldsMap.put(player.getWorld().getName(),
new ChatConfig(ConfigManager.getConfigFile(player.getWorld().getName())));
return worldsMap.get(player.getWorld().getName());
synchronized (worldsMap) {
if (!worldsMap.containsKey(player.getWorld().getName()))
worldsMap.put(player.getWorld().getName(),
new ChatConfig(ConfigManager.getConfigFile(player.getWorld().getName())));
return worldsMap.get(player.getWorld().getName());
}
}
public final boolean arrivalsCheck;

View File

@ -30,7 +30,7 @@ public class ChatData {
* the player
* @return the data
*/
public static ChatData getData(final Player player) {
public synchronized static ChatData getData(final Player player) {
if (!playersMap.containsKey(player.getName()))
playersMap.put(player.getName(), new ChatData());
return playersMap.get(player.getName());
@ -59,7 +59,7 @@ public class ChatData {
/**
* Clear the data of the no pwnage check.
*/
public void clearNoPwnageData() {
public synchronized void clearNoPwnageData() {
noPwnageCaptchTries = noPwnageReloginWarnings = 0;
noPwnageJoinTime = noPwnageLastMessageTime = noPwnageLastMovedTime = noPwnageLastWarningTime = noPwnageLeaveTime = noPwnageReloginWarningTime = 0L;
noPwnageGeneratedCaptcha = noPwnageLastMessage = "";

View File

@ -59,7 +59,7 @@ public class ChatListener implements Listener {
event.setMessage(color.check(player, event.getMessage()));
// Then the no pwnage check.
if (noPwnage.isEnabled(player) && noPwnage.check(player))
if (noPwnage.check(player, event, false))
player.kickPlayer(Check.removeColors(ChatConfig.getConfig(player).noPwnageKickMessage));
}
@ -105,7 +105,7 @@ public class ChatListener implements Listener {
event.setMessage(color.check(player, event.getMessage()));
// Then the no pwnage check.
if (noPwnage.isEnabled(player) && noPwnage.check(player))
if (noPwnage.check(player, event, true))
player.kickPlayer(Check.removeColors(ChatConfig.getConfig(player).noPwnageKickMessage));
}
@ -127,7 +127,7 @@ public class ChatListener implements Listener {
* |___/
*/
final Player player = event.getPlayer();
final ChatConfig cc = ChatConfig.getConfig(player);
final ChatConfig cc = ChatConfig.getConfig(player); // Non critical use (concurrency).
// First the arrivals check, if enabled of course.
if (arrivals.isEnabled(player) && arrivals.check(player))
@ -135,7 +135,7 @@ public class ChatListener implements Listener {
event.disallow(Result.KICK_OTHER, cc.arrivalsMessage);
// Then the no pwnage check, if the login isn't already disallowed.
if (event.getResult() != Result.KICK_OTHER && noPwnage.isEnabled(player) && noPwnage.check(player))
if (event.getResult() != Result.KICK_OTHER && noPwnage.check(player))
event.disallow(Result.KICK_OTHER, cc.noPwnageReloginKickMessage);
}
}

View File

@ -11,6 +11,7 @@ import org.bukkit.event.player.PlayerEvent;
import fr.neatmonster.nocheatplus.actions.ParameterName;
import fr.neatmonster.nocheatplus.checks.Check;
import fr.neatmonster.nocheatplus.checks.CheckType;
import fr.neatmonster.nocheatplus.checks.ExecuteActionsEvent;
import fr.neatmonster.nocheatplus.utilities.CheckUtils;
/*
@ -49,48 +50,70 @@ public class NoPwnage extends Check {
public NoPwnage() {
super(CheckType.CHAT_NOPWNAGE);
for (final Player player : Bukkit.getOnlinePlayers())
ChatData.getData(player).noPwnageLastLocation = player.getLocation();
for (final Player player : Bukkit.getOnlinePlayers()){
final ChatData data = ChatData.getData(player);
synchronized(data){
data.noPwnageLastLocation = player.getLocation();
}
}
}
/**
* Checks a player (join).
*
* Only called from the main thread.
*
* @param player
* the player
* @return true, if successful
*/
public boolean check(final Player player) {
if (!isEnabled(player)) return false;
final ChatConfig cc = ChatConfig.getConfig(player);
final ChatData data = ChatData.getData(player);
boolean cancel = false;
final long now = System.currentTimeMillis();
// NoPwnage will remember the time when a player leaves the server. If he returns within "time" milliseconds, he
// will get warned. If he has been warned "warnings" times already, the "commands" will be executed for him.
// Warnings get removed if the time of the last warning was more than "timeout" milliseconds ago.
if (cc.noPwnageReloginCheck && now - data.noPwnageLeaveTime < cc.noPwnageReloginTimeout) {
if (now - data.noPwnageReloginWarningTime > cc.noPwnageReloginWarningTimeout)
data.noPwnageReloginWarnings = 0;
if (data.noPwnageReloginWarnings < cc.noPwnageReloginWarningNumber) {
player.sendMessage(replaceColors(cc.noPwnageReloginWarningMessage));
data.noPwnageReloginWarningTime = now;
data.noPwnageReloginWarnings++;
} else if (now - data.noPwnageReloginWarningTime < cc.noPwnageReloginWarningTimeout)
// Find out if we need to ban the player or not.
cancel = executeActions_(player);
synchronized(data){
return unsafeCheck(player, cc, data);
}
// Store his location and some other data.
data.noPwnageLastLocation = player.getLocation();
data.noPwnageJoinTime = now;
return cancel;
}
/**
* Check (Join), only call from synchronized code.
* @param player
* @param cc
* @param data
* @return
*/
private boolean unsafeCheck(final Player player, final ChatConfig cc, final ChatData data) {
boolean cancel = false;
final long now = System.currentTimeMillis();
// NoPwnage will remember the time when a player leaves the server. If he returns within "time" milliseconds, he
// will get warned. If he has been warned "warnings" times already, the "commands" will be executed for him.
// Warnings get removed if the time of the last warning was more than "timeout" milliseconds ago.
if (cc.noPwnageReloginCheck && now - data.noPwnageLeaveTime < cc.noPwnageReloginTimeout) {
if (now - data.noPwnageReloginWarningTime > cc.noPwnageReloginWarningTimeout)
data.noPwnageReloginWarnings = 0;
if (data.noPwnageReloginWarnings < cc.noPwnageReloginWarningNumber) {
player.sendMessage(replaceColors(cc.noPwnageReloginWarningMessage));
data.noPwnageReloginWarningTime = now;
data.noPwnageReloginWarnings++;
} else if (now - data.noPwnageReloginWarningTime < cc.noPwnageReloginWarningTimeout)
// Find out if we need to ban the player or not.
cancel = executeActionsThreadSafe(player, true);
}
// Store his location and some other data.
data.noPwnageLastLocation = player.getLocation();
data.noPwnageJoinTime = now;
return cancel;
}
/**
* Checks a player (chat).
*
* @param player
@ -99,10 +122,29 @@ public class NoPwnage extends Check {
* the event
* @return true, if successful
*/
public boolean check(final Player player, final PlayerEvent event) {
public boolean check(final Player player, final PlayerEvent event, final boolean isMainThread) {
if (isMainThread && !isEnabled(player)) return false;
final ChatConfig cc = ChatConfig.getConfig(player);
final ChatData data = ChatData.getData(player);
data.noPwnageVL = 0D;
synchronized(data){
return unsafeCheck(player, event, isMainThread, cc, data);
}
}
/**
* Only to be called form synchronized code.
* @param player
* @param event
* @param isMainThread
* @param cc
* @param data
* @return
*/
private boolean unsafeCheck(final Player player, final PlayerEvent event, final boolean isMainThread, final ChatConfig cc, final ChatData data) {
data.noPwnageVL = 0D;
boolean cancel = false;
@ -126,7 +168,7 @@ public class NoPwnage extends Check {
// Does he failed too much times?
if (data.noPwnageCaptchTries > cc.noPwnageCaptchaTries)
// Find out if we need to ban the player or not.
cancel = executeActions_(player);
cancel = executeActionsThreadSafe(player, isMainThread);
// Increment his tries number counter.
data.noPwnageCaptchTries++;
@ -223,7 +265,7 @@ public class NoPwnage extends Check {
((PlayerCommandPreprocessEvent) event).setCancelled(true);
// Find out if we need to ban the player or not.
cancel = executeActions_(player);
cancel = executeActionsThreadSafe(player, isMainThread);
}
// Store the message and some other data.
@ -234,21 +276,41 @@ public class NoPwnage extends Check {
}
return cancel;
}
}
@Override
public final boolean executeActions(final Player player){
// To be called from synchronized code (ChatData).
// Late check of bypass permissions:
// (One might use a bypass flag, set if its already been checked and then reset.)
if (!isEnabled(player)) return false;
return super.executeActions(player);
}
/**
* Execute actions.
*
* Execute actions from another thread (not the main thread).<br>
* This does not use extra synchronization.
* @param player
* the player
* @return true, if successful
* @return
*/
private boolean executeActions_(final Player player) {
if (super.executeActions(player)) {
ChatData.getData(player).clearNoPwnageData();
return true;
}
return false;
public final boolean executeActionsThreadSafe(final Player player, boolean isMainThread){
if (isMainThread){
// Just execute.
if (executeActions(player)){
ChatData.getData(player).clearNoPwnageData();
return true;
}
else
return false;
}
else {
// Sync it into the main thread by using an event.
final ExecuteActionsEvent event = new ExecuteActionsEvent(this, player);
Bukkit.getPluginManager().callEvent(event);
final boolean cancel = event.getCancel();
if (cancel) ChatData.getData(player).clearNoPwnageData();
return cancel;
}
}
/* (non-Javadoc)
@ -261,4 +323,5 @@ public class NoPwnage extends Check {
else
return super.getParameter(wildcard, player);
}
}