XOR toggle an Enum value in or out of an EnumSet

135 views Asked by At

I am refactoring some code that used low-level bit manipulation and masking to reimplement it with an EnumSet. The incentive is: having a much more maintainable and readable code. I find it appropriate to abstract the logic away from low-level implementation like bits and memory units, while trading some minor efficiency loss (if any).

I already wrote the base methods to set, unset various states to replace bits. But I also need a method to flip a flag (set if unset, unset if set).

Here is a simplified subset of my class. It handles the state of input restriction per side of a block in a MinecraftForge mod (if context matters). Most of the time, player interacts with the setting of the side restriction, to flip it one way or the other (thus using the flip() method).

public class InputRestrictor {

    protected EnumSet<ForgeDirection> sideRestricted = EnumSet.noneOf(ForgeDirection.class);

    public void set(final ForgeDirection side, final boolean isInputOnly) {
        if (isInputOnly) sideRestricted.add(side);
        else sideRestricted.remove(side);
    }

    public boolean get(final ForgeDirection side) {
        return sideRestricted.contains(side);
    }

    public void flip(final ForgeDirection side) {
        set(side, !get(side));
    }
}

I think if the EnumSet had a native method to flip an enum value in-out of the set, it would be more efficient than first fetching the boolean state, inverting it and setting it or removing it conditionally.

I read in this post Why an EnumSet or an EnumMap is likely to be more performant than their hashed counterparts?, an EnumSet is internally implemented using a bit-mask or bit-field. Would be convenient if one could just toggle XOR one or more enum values (in or out of the set) in one step like you'd do with:

byte bits = 0b000111;
bits ^= (byte) 1 << 2; // inverted bit 2: bits = 0b00011
bits ^= (byte) 0b111111; // inverted all at once: bits = 0b111100

Are there more direct toggling methods beside going low level with bit manipulation?

2

There are 2 answers

0
Louis Wasserman On BEST ANSWER

No. EnumSet only exposes the basic Set methods, which do not include a toggle of the form you describe.

I would suspect that the reason it does not provide such a method is that it would be very rarely used. Most usages of Sets -- certainly those that'd require high performance -- seem likely to only need to add and remove.

2
Léa Gris On

Given my requirements to abstract away from the implementation, keep decent performances and Louis's answer confirming the absence of toggling in the EnumSet implementations; There is a good middle-ground for my use case.

The implementation details can be moved away from the main class, while still flipping bits.

I found it out when I realised my abstraction could be reused for other kind of enumerated boolean properties packed into a binary number, where those also need a byte representation of the sided boolean state : to transmit, save and load persistent data in a minimal byte space.

Rewrote it this way:

public class EnumBoolean<T extends Enum<T>> {

    public long bits = 0;

    public void set(final T enumValue) {
        bits |= (1L << enumValue.ordinal());
    }

    public void clear(final T enumValue) {
        bits &= ~(1L << enumValue.ordinal());
    }

    public void setBoolean(final T enumValue, final boolean boolValue) {
        if (boolValue) set(enumValue);
        else clear(enumValue);
    }

    public boolean get(final T enumValue) {
        return (bits & (1L << enumValue.ordinal())) != 0;
    }

    public void toggle(final T enumValue) {
        bits ^= (1L << enumValue.ordinal());
    }
}

Then the byte bit state can be obtained by casting, saved and reloaded very efficiently.

If I had persisted with the EnumSet it would have required looping through values, turn it to bit and the other way to load. Or worse overkill implementation with a BitSet of the persistence byte, which uses and returns an array. (way too much bloat for 6-bits). I examine the RegularEnumSet implementation, and while switching bits internally, it also contains lots of boilerplate, type testing and checks that are useless bloat for this use case.

EnumBoolean<ForgeDirection> disabledInput = new EnumBoolean<>();

// yada yada yada

public void saveNBTData(final NBTTagCompound nbtTagCompound) {
   nbtTagCompound.setByte("disabledInput", (byte) disabledInput.bits);
}

public void loadNBTData(final NBTTagCompound nbtTagCompound) {
    disabledInput.bits = nbtTagCompound.getByte("disabledInput");
}