How to convert to ASyncPlayerChatEvent?

Discussion in 'Plugin Development' started by McLuke500, Aug 8, 2012.

Thread Status:
Not open for further replies.
  1. Offline

    McLuke500

    Basically my plugin does alot on player chat.
    Code:
    EventHandler(priority = EventPriority.NORMAL)
        public void onPlayerChat(PlayerChatEvent event) {
            Player player = event.getPlayer();
            World world = player.getWorld();
            String worldname = world.getName();
            String playername = player.getName();
            String msg = event.getMessage();
            Boolean staff = false;
            double x = (int) Math.floor(player.getLocation().getX());
            double y = (int) Math.floor(player.getLocation().getY());
            double z = (int) Math.floor(player.getLocation().getZ());
            if (plugin.getConfig().getBoolean("Log.PlayerChat")) {
                if (player.hasPermission("PlayerLogger.staff")) {
                    staff = true;
                }
                if (plugin.getConfig().getBoolean("File.LogToFiles")) {
                    filehandler.logChat(playername, msg, worldname, x, y, z, staff);
                }
                if (plugin.getConfig().getBoolean("MySQL.Enabled")) {
                    playerlogger.addData(playername,"chat", msg, x, y, z, worldname, staff);
                }}}
    Then if logtofiles is enabled
    Code:
    //Chat
        public static void logChat(String playername, String msg, String worldname,
                double x, double y, double z, Boolean staff) {
            Boolean lowercase = getLowercase();
            if (lowercase) {
                playername = playername.toLowerCase();
            }
            File user;
            if (staff == true && getStaff()) {
                user = new File("plugins/PlayerLogger/Staff/" + playername + ".properties");
            } else if (!getOnlyStaff()){
                user = new File("plugins/PlayerLogger/Users/" + playername + ".properties");   
            } else {
                return;
            }
            try {
                FileWriter outfile = new FileWriter(user, true);
                PrintWriter out = new PrintWriter(outfile);
                user.createNewFile();
     
                out.println("["+worldname+"]"+playername + " said: " + msg + " " + " " + "(" + (int)x + " " + (int)y + " " + (int)z + ") ("+getTimestamp()+")");
                out.close();
            }catch (IOException e) {
                e.printStackTrace();
            }}
    Then if mysql is enabled
    Code:
        //MySQL
        public static void addData(String playername, String type, String data, double x, double y, double z, String worldname, Boolean staff) {
            PreparedStatement pst = null;
            Connection con = null;
            long time = System.currentTimeMillis()/1000; //Unix time
            //Checking if they should be logged
            if (staff && plugin.getConfig().getBoolean("Log.LogOnlyStaff") || !plugin.getConfig().getBoolean("Log.LogOnlyStaff")) {
     
     
     
                try {
                    con = DriverManager.getConnection("jdbc:mysql://" + plugin.getConfig().getString("MySQL.Server") + "/" + plugin.getConfig().getString("MySQL.Database"), plugin.getConfig().getString("MySQL.User"), plugin.getConfig().getString("MySQL.Password"));
     
                    String database = "playerlogger";
                    //Prepared statment
                    pst = con.prepareStatement("INSERT INTO " +database+"(playername, type, time, data, x, y, z, world) VALUES(?, ?, ?, ?, ?, ?, ?, ?)");
                    //Values
                    pst.setString(1, playername);
                    pst.setString(2, type);
                    pst.setLong(3, time);
                    pst.setString(4, data);
                    pst.setDouble(5, x);
                    pst.setDouble(6, y);
                    pst.setDouble(7, z);
                    pst.setString(8, worldname);
                    //Do the MySQL query
                    pst.executeUpdate();
                } catch (SQLException ex) {
                    System.out.print(ex);
                }
            }   
        }

    So how do I make that thread safe :confused:?
     
  2. Offline

    Sagacious_Zed Bukkit Docs

    Multi threading is a subtle problem.
    You need to separate your event, into what needs to be checked. the criteria you are checking against. and what needs to be done in response.
     
  3. Offline

    McLuke500

    I still dont get it ;(
     
  4. Offline

    bitfreeze

    Well, even before the advent of AsynchPlayerChatEvent your listener caused a small lag for each chat line sent by a player. If both File and SQL logs were enabled, only after both were saved the event would end. That may not be an issue if you only have just a few players and the file system and SQL connections are fast. But have 100+ players chatting together or a slow disk / connection, and things may start going awry.

    Since you did not depend on the success of logging to allow the chat to be sent you could have done the I/O (file, sql) in a separate asynchronous task.

    Now with the asynch event, I suggest you to call a synch routine to perform all the thread-safe stuff that you need to check/gather, then from it call async routines to perform the outputs.

    Another hint for performance: store the important config values in static variables so you can check them from the asynch thread by looking at those variables instead of accessing the getConfig directly all the time. I'm not sure whether getConfig() would be unsafe in a thread, but despite that comparing a simple boolean variable is faster than retrieving that setting over and over from the config whenever a chat is sent. Don't forget to update those variables whenever the config is reloaded!

    In a macro view:

    Code:
    AsynchPlayerChatEvent event
        if Main.isLogEnabled && (Main.isFileEnabled || Main.isSQLEnabled)
            Schedule SynchThread(plugin, event.player, event.getMessage())
     
    SynchThread
        Prepare common values
        if Main.isFileEnabled
            Schedule AsynchTread1(data)
        if Main.isSQLEnabled
            Schedule AsynchTread2(data)
     
    AsynchThread1
        Save log to File
     
    AsynchThread2
        Save log to SQL
    
     
    McLuke500 likes this.
  5. Offline

    McLuke500

    I get you now, would I have to do this with every event? Or just the chat. I will add static booleans instead of accessing config for all my config options, I saw how organising everything in folders looks nicer so I think I'm gonna have every listener in a separate class, the command in its own folder, and make it more organised such as untils having MySQL and filed handlers in it.
    How would I access classes in other folders Ik I can just do main.booleanname but for if it's in a folder is it just like listener.chatlistener.publicboolean?
     
  6. Offline

    bitfreeze

    Basically it depends on the frequency that event is called, and whether your event calls heavy blocks of code like writing to a file or storing to sql.

    The new asynch chat event was made to let chat flow steadily even if the server happens to be laggy. Adding heavy processing to that event is going against the whole idea of it being made asynch. Have in mind that for the chat to be "approved" or sent, it depends on this event to end.

    So far only two events were made asynchronous, Chat and PreLogin. If you hook to them, try to be lightweight.

    If you're talking about the config booleans I suggested, I would store them either in the main class of your plugin or in a class dedicated to settings, if there's one.

    To access objects from other folders or packages, import them:
    Code:
    package me.mclucke500.MyLogger
    MainClass extends JavaPlugin
        private static boolean logEnabled
        public static boolean isLogEnabled()
            return logEnabled
     
    package me.mclucke500.MyLogger.listener
    import me.mclucke500.MyLogger.MainClass
    ListenerClass
        AsynchPlayerChatEvent event
            if MainClass.isLogEnabled()
    
     
  7. Offline

    McLuke500

    I will work on the update tonight, you have been incredibly helpful thanks!!!
     
  8. Offline

    bitfreeze

  9. Offline

    McLuke500

    bitfreeze

    Code:
        //Player Chat
        @EventHandler(priority = EventPriority.NORMAL)
        public void onPlayerChat(final AsyncPlayerChatEvent event) {
            plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() {
     
                public void run() {
                    final Player player = event.getPlayer();
                    World world = player.getWorld();
                    final String worldname = world.getName();
                    final String playername = player.getName();
                    final String msg = event.getMessage();
                    final double x = (int) Math.floor(player.getLocation().getX());
                    final double y = (int) Math.floor(player.getLocation().getY());
                    final double z = (int) Math.floor(player.getLocation().getZ());
                    if (plugin.getConfig().getBoolean("File.LogToFiles")) {
                        plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin,new Runnable(){
                           
                        public void run() {
                            Boolean staff = false;
                            if (player.hasPermission("PlayerLogger.staff")) {
                                staff = true;
                            }
                            filehandler.logChat(playername, msg, worldname, x, y, z, staff);
                        }}, 1L);
                    }
                    if (plugin.getConfig().getBoolean("MySQL.Enabled")) {
                   
                        plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin,new Runnable(){
                           
                            public void run() {
                                Boolean staff = false;
                                if (player.hasPermission("PlayerLogger.staff")) {
                                    staff = true;
                                }
                                playerlogger.addData(playername,"chat", msg, x, y, z, worldname, staff);
                            }}, 1L);
                       
                    }
                }
            }, 1L);

    Is that right or am I doing something wrong?
     
  10. Offline

    bitfreeze

    I guess. Worth a test now.
    Maybe you'll prefer to use this handler instead:
    Code:
    @EventHandler(priority = EventPriority.MONITOR, ignoreCancelled = true)
     
  11. Offline

    McLuke500

    Ahh Yes I will set them all to moniter soon But I wont ignore cancelled for chat as many chat plugins such as ichat cancel it to add prefixs and such :D

    Does monitor faster? Ik you should always use it if you not going to change the event but is it faster in anyway?
    Aslo I tried spamming This is a test the words on separate lines and it went out of sync which it opulent of done in the past is there a fix? I mean like chatting 20 times a second to that extreme :D

    EDIT by Moderator: merged posts, please use the edit button instead of double posting.
     
    Last edited by a moderator: May 27, 2016
  12. Offline

    bitfreeze

    Not faster but it runs after all other event levels, so in the MONITOR you would have the event's final cancel state...

    Not sure if I got this part, sorry. Out of sync with it opulent of done in the past?
     
  13. Offline

    McLuke500

    I spammed
    This
    Is
    A
    Test
    This
    Is
    A
    Test ect

    But in the log file andmmysql it started ok but went to like
    This
    Test
    Is
    A
    Test
    Is
    A
    This
     
  14. Offline

    bitfreeze

    Welcome to the asynchronous events :D
    There are some ways to try and avoid that tho, like adding the texts to a sequential list and having the scheduled task to always use the oldest record from the list...
    For the SQL part that would not be necessary, if you're adding a timestamp to the records (saved when the event is called).
     
  15. Offline

    McLuke500

    Hmm the timestamp is called when the MySQL event happens :D I'll change it just for chat
     
  16. Offline

    bitfreeze

    To keep a neat other in SQL you can get the timestamp in the beginning of the AsyncChat event, forwarding it to the other pieces of code.
     
  17. Offline

    McLuke500

    Not sure if I worded it right but I get the timestamp just before the query is sent as AddData is called. The timestamp is only got in the file handler class and the MySQL class not the listener class
     
  18. Offline

    bitfreeze

    Exactly, so if the async runnables run at different orders, the sequence of your logs will be compromised bigtime.
    Hence my suggestion to save the timestamp on the main AsyncChat, to have the sequence as close as posssible to the real thing.
     
Thread Status:
Not open for further replies.

Share This Page