Is it worth optimizing multiple bools in a component to a bitmask?

I have a component that currently holds 14 bool variables. I was wondering if burst is able to automatically optimize this away to 1 bit per bool, instead of 1 byte per bool or even 4 bytes per bool (according to certain sources)?

If not then I would probably want to store them all in the form of a 2-bytes (16 bit) bitmask. And are there any existing tools in UnityPackages for creating a bitmask from multiple bools?

1 Like

Hello, @PhilSA , looks like Unity.Collections.BitField32 and Unity.Collections.BitField64 is what you are looking for.

Also, BitVector32 Struct (System.Collections.Specialized) | Microsoft Learn

Or you can write your own :slight_smile:
set: bitMask |= 1 << pos;
get: bitMask & ~(1 << pos) != 0

6 Likes

IComponentData layout is defined by the C# standard. bools are 2 byte large.
What Kelevra said is good advice.

3 Likes

If you have things slightly larger than a bool but smaller than a byte (or other awkward sizes like 10 bits), and really need to pack them like C bitfields, I made this:

https://gitlab.com/burningmime/easybuilding/-/blob/master/Assets/src/PackBits.csx

(made it kinda quickly so the code it generates is ugly but works. EDIT: fixed it so it works for bools also)

1 Like

I think bools are defined as 1 byte in C# (Docs)

but differ for other languages, e.g. VB (Docs)

Are Unity.mathematics Bool4 and others optimized for memory with burst ?
looking there https://github.com/Unity-Technologies/Unity.Mathematics/blob/master/src/Unity.Mathematics/bool4.gen.cs
it doesnt seem so

[MarshalAs(UnmanagedType.U1)]
public bool x;

btw this can be useful :

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool IsBitSet(byte b, int pos) { return (b & (1 << pos)) != 0; }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static bool BitSame(byte b, int posa, int posb) { return (b & (1 << posa)) == (b & (1 << posb)); }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void SetBit(ref byte b, int pos) { b |= (byte)(1 << pos); }

        [MethodImpl(MethodImplOptions.AggressiveInlining)]
        public static void ClearBit(ref byte b, int pos) { b &= (byte)~(1 << pos); }
4 Likes

JacksonDunstan.com | BitArray32 and BitArray64 has some implementations also.

1 Like

An alternative is to use enums with the Flags attribute instead of a BitField32/64. It also comes with type safety features, lets you define the size of the field, and lets you name each individual flags, which might lend some readability to whatever you are writing.

2 Likes

Unity.Collections has several options for this actually:

NativeBitArray: Struct NativeBitArray | Collections | 0.12.0-preview.13

BitField32: Struct BitField32 | Collections | 0.12.0-preview.13
BitField64: Struct BitField64 | Collections | 0.12.0-preview.13

The nice thing about the BitField* types is you should be able to use Explicit struct layouts to create a union with an ordinary value of the type for serialization. I haven’t tested this out myself, but I am looking at getting rid of my custom bit manipulation code in favor of BitField and friends.

NativeBitArray seems to be an arbitrary sized array, and there’s conversion tools for converting a block of data to a NativeBitArray.

be careful with that:
" Burst doesn’t currently support Enum methods (e.g Enum.HasFlag)"
https://docs.unity3d.com/Packages/com.unity.burst@1.4/manual/docs/CSharpLanguageSupport_Types.html#enum-types

It seems to support bitwise operations just fine. Casts to and from the underlying type seem to be no-ops when compiled, and it still guarantees a certain level of type safety when reading/writing values. If you are using the flags more widely, resizing it to be larger is easier just by changing the underlying type the flags use. Here’s an excerpt of how I structure the flags on my players:

[Flags]
public enum PlayerFlags : byte {
  FACING_LEFT   = 1 << 0,
  GROUNDED      = 1 << 1,
  FAST_FALLING  = 1 << 2,
  TEETERING     = 1 << 3,
  HAS_DIED      = 1 << 4,
  HAS_RESPAWNED = 1 << 5,
  EVENT_FLAGS   = HAS_DIED | HAS_RESPAWNED,
}

public struct PlayerComponent : IComponentData {

  public PlayerFlags Flags;                   // 1 byte

  public bool Is(PlayerFlags mask) => (Flags & mask) != 0;
  public void SetFlags(PlayerFlags mask) => Flags |= mask;
  public void UnsetFlags(PlayerFlags mask) => Flags &= ~mask;
}
4 Likes