0

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?

Léa Gris
  • 17,497
  • 4
  • 32
  • 41
  • 3
    A little wonky, but how about: `if (!sideRestricted.remove(side)) { sideRestricted.add(side); }`. You want to avoid iterating the set to find it, but unless you extended the class I don't think there's a direct swap available. Note that operations on `EnumSet` are _extremely_ fast (it's bit logic on a `long` using `Enum#ordinal`, so extending would avail you that ability). – Rogue Apr 24 '23 at 18:28
  • To do the bit flip in the case of extending, you'd want `this.set ^= yourEnumConstant.ordinal()`, where `this.set` would be the bitfield/`long` held by `EnumSet`. Note even with the direct bit manipulation, there's still at least the three read-update-write operations. Compound assignment operators are not atomic, so (slightly) not thread safe here – Rogue Apr 24 '23 at 20:36

2 Answers2

1

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.

Louis Wasserman
  • 191,574
  • 25
  • 345
  • 413
0

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");
}
Léa Gris
  • 17,497
  • 4
  • 32
  • 41
  • 1
    You can change `ForgeDirectionBoolean` to `CustomEnumSet>`, and `ForgeDirection` to `T`, and you've basically got just that: your very own `EnumSet` class. – Rogue Apr 25 '23 at 13:23
  • @Rogue, thank you so much. I genericized it to your recommendation and now I can reuse it for many kinds of Enumerated booleans of up to 64 values. Which is overkill but leave plenty of margin in my use case. – Léa Gris Apr 25 '23 at 15:03