Solved Nitpicking - Performance of a String-related code snippet

Discussion in 'Spigot Plugin Development' started by jetp250, Apr 15, 2017.

  1. Okay. I have this setup for NMS mobs and all that fancy stuff now. I use a modified ArmorStand as a healthbar that continuously updates its nametag based on the entity's health.

    Now, there can be tens, hundreds or thousands of these entities depending on the situation (most likely every single entity will have a healthbar..), so needless to say I'm kind-of worried about the performance of the healthbar.

    Here's my HealthBar class:
    Code (Java):
    public class HealthBar extends EntityArmorStand {

        private final EntityInsentient parent;
        private final int updateRate;
        private final int maxHealth;
        private final float yOffset;
        private float prevHealth;
        private char[] charArray;
        private boolean spawned;

        public HealthBar(World world, EntityInsentient parentEntity) {
            super(world);
            this.parent = parentEntity;
            this.maxHealth = MathHelper.floor(parent.getMaxHealth());
            this.charArray = new char[maxHealth + 4]; // Color codes take up 4 chars
            this.setCustomNameVisible(true);
            this.yOffset = parent.getHeadHeight() + 0.1f;
            this.updateRate = TDConfiguration.healthbarUpdateRate;
        }
       
        @Override
        public void A_() { // This is the only code executed on each tick, since I'm not calling super.A_();
            this.locX = parent.locX;
            this.locZ = parent.locZ;
            this.locY = parent.locY + yOffset;
            if (spawned && parent.dead)
                this.die();
            if (ticksLived % updateRate == 0 && prevHealth != parent.getHealth()) {
                this.charArray = MobUtility.updateHealthbar(charArray, prevHealth = parent.getHealth());
                this.setCustomName(new String(charArray)); // Turning the array to a String..
            }
        }
    }
    and, most importantly, the MobUtility.updateHealthBar method:
    Code (Java):
    public static char[] updateHealthbar(final char[] characters, final float health) {
            characters[0] = '\u00a7';
            characters[1] = 'a';
            boolean colorAdded = false;
            for (int i = 2; i < characters.length; ++i) {
                if (!colorAdded && health <= i-2 && i < characters.length-1) {
                    colorAdded = true;
                    characters[i] = '\u00a7';
                    characters[++i] = '7';
                    continue;
                }
                characters[i] = '.';
            }
            return characters;
        }
    Yes, it's pretty darn hard-coded right now. Don't worry about that.
    Now, it's not slow I guess, but is there anything that I could do to hopefully improve it even more, mainly on the updateHealthbar?
    Or, is this somewhat the fastest way I could do this, and should I just stop worrying about such things..?
    Thanks!
     
  2. can't you literally do this in constant time? (i.e, changing 4 characters in the array, regardless of what the input is). Fill the char[] with your health bar character, set the first two to the bar colour (green?) and the last two to the empty colour (red?), to signify a full bar (since the red will not be rendered).

    Whenever the health changes, take the old health to figure out the old indices of the empty colour (two, as colouring takes two characters), and set those two to the health bar character. Then compute the new indices, and set those to the empty colour.

    (exact implementation obviously depends a bit on whether the bar goes from left-to-right or right-to-left, etc)
     
  3. Thank you for the answer!
    Unfortunately though, since the health change can be anything from 0 to mob's max health, I don't think that's going to work - if I understood right what you meant.

    To do what you said, I'd have to;
    - Check where the previous color codes are and remove them
    - Re-set the previous color codes to dots - in this case
    - Find the new location of the color codes and replace the dots at that location to color codes

    .. which includes comparing and most likely more processing than what I now have.

    But now that I think about it..
    To make it a bit easier to do, I could always cache the location of the 'red' color code and pass that as an argument, so I wouldn't need the comparing of characters. This could actually work. I'll be back in a moment :eek:
     
  4. Oh yeah. Much better. Here's what I came up with:
    Code (Java):
    public void updateHealthbar() {
            final int healthFloored = MathHelper.floor(prevHealth = parent.getHealth()) + 2;
           
            charArray[lastIndex] = charArray[lastIndex + 1] = '.';
            charArray[lastIndex = healthFloored] = '\u00a7';
            charArray[healthFloored + 1] = '7';
        }
    The full code and implementation:
    Code (Java):
    public class HealthBar extends EntityArmorStand {

        private final EntityInsentient parent;
        private final int updateRate;
        private final int maxHealth;
        private final float yOffset;
        private float prevHealth;
        private char[] charArray;
        private boolean spawned;

        // The last color code index
        private int lastIndex;

        public HealthBar(final World world, final EntityInsentient parentEntity) {
            super(world);
            this.parent = parentEntity;
            this.noclip = true;
            this.collides = false;
            this.maxHealth = MathHelper.floor(parent.getMaxHealth());
            this.charArray = new char[maxHealth + 4];

            // a bit messy setup.
            charArray[0] = '\u00a7';
            charArray[1] = 'a';
            for (int i = 2; i < charArray.length - 2; i++)
                charArray[i] = '.';
            charArray[lastIndex = charArray.length - 2] = '\u00a7';
            charArray[charArray.length - 1] = '7';

            this.setCustomNameVisible(true);
            this.setInvisible(true);
            this.setMarker(true);
            this.yOffset = parent.getHeadHeight() + 0.1f;
            this.updateRate = TDConfiguration.healthbarUpdateRate;
            this.updateEffects = false;
        }

        @Override
        public final void A_() {
            this.locX = parent.locX;
            this.locZ = parent.locZ;
            this.locY = parent.locY + yOffset;
            if (spawned && parent.dead)
                this.die();
            if (ticksLived % updateRate == 0 && prevHealth != parent.getHealth()) {
                updateHealthbar();
                this.setCustomName(new String(charArray));
            }
        }

        public void updateHealthbar() {
            final int healthFloored = MathHelper.floor(prevHealth = parent.getHealth()) + 2;
         
            charArray[lastIndex] = charArray[lastIndex + 1] = '.';
            charArray[lastIndex = healthFloored] = '\u00a7';
            charArray[healthFloored + 1] = '7';
        }
    Thanks @DarkSeraphim !
     
    • Friendly Friendly x 1