Bitmasker

Bitmasker

Small helper class to track ‘flagged’ state. Works together with an enum and provides easy toggled/not toggled checks.


Basic Usage

enum MyColors {
  Red,
  Green,
  Blue,
}

const mask = new Bitmasker<MyColors>();

// `set` lets you update the mask
mask.set(MyFlags.Red, MyFlags.Blue);
mask.has(MyFlags.Red) === true;
mask.has(MyFlags.Green) === false;
mask.has(MyFlags.Blue) === true;

// You can also use the NOT operator (~) to unset or check for the absence of a flag:
mask.set(~MyFlags.Red, MyFlags.Green);
// or use `unset` itself:
mask.unset(MyFlags.Blue);

mask.has(MyFlags.Red) === false;
mask.has(MyFlags.Green) === true;
mask.has(~MyFlags.Blue) === true; // note the NOT (~) operator

// You can also compare a bunch of flags at once, including NOT'd flags. This uses the 'AND' operator.
mask.all(~MyFlags.Red, MyFlags.Green) === true;
mask.all(MyFlags.Red, MyFlags.Green, MyFlags.Blue) === false;

// if you want an 'OR' check against a bunch of flags instead, you can use `some`:
mask.some(MyFlags.Red, MyFlags.Green) === true; // Red is not set, but Green is - returning true!
mask.some(~MyFlags.Red, ~MyFlags.Green) === true; // Red is not set - therefor SOME of the requirements are satisfied!

// You can also reset the mask via `clear`:
mask.clear();

Practical Example (Character Traits)

In this example, we use the bitmasker as a state tracker for a video game character’s “traits.” This allows us to attach/remove multiple traits to a given character, and later test for individual or specific combinations of those traits. This allows us to detect when the character is invulnerable, for instance, and can not take damage. This is more practical than, say, having a boolean on the character denoting invulnerability, because in order to add more possible traits, you just update the enum.


enum CharacterTraits {
  Flying,
  Invulnerable,
  ImmuneToPoison,
  CanBreatheUnderWater,
  // ..etc..
}

class Character {
  public traits: Bitmasker<CharacterTraits> = new Bitmasker();
  // ... onDamaged, applyDamage, onPickup, etc ...
}

const mario = new Character();
mario.onDamaged = (type, amount) => {
  if (mario.traits.has(CharacterTraits.Invulnerable)){
    // can't be hurt at all!
    return;
  }
  if (type == POISON && mario.traits.has(CharacterTraits.ImmuneToPoison)){
    // can't be poisoned!
    return;
  }
  mario.applyDamage(type, amount);
};

mario.onPickup = (pickup) => {
  switch(pickup.type){
    case WINGS: mario.traits.set(CharacterTraits.Flying); break;
    case STAR: mario.traits.set(CharacterTraits.Invulnerable); break;
  }
}

mario.onPickupEffectExpire = (pickup) => {
  switch(pickup.type){
    case WINGS: mario.traits.unset(CharacterTraits.Flying); break;
    case STAR: mario.traits.unset(CharacterTraits.Invulnerable); break;
  }
}

Implementation

export default class Bitmasker<E> {
  private flags: number;

  constructor() {
    this.flags = 0;
  }

  // Sets a flag corresponding to an enum value
  public set(...flags: E[]): void {
    for (const flag of flags) {
      const f = Number(flag);

      if (f >= 0) {
        this.flags |= (1 << f);
      } else {
        this.flags &= ~(1 << (~f));
      }
    }
  }

  // Clears a flag corresponding to an enum value
  public unset(...flags: E[]): void {
    if (flags.length === 0) {
      this.flags = 0;
      return;
    }

    for (const flag of flags) {
      this.flags &= ~(1 << Number(flag));
    }
  }

  // Toggles a flag corresponding to an enum value
  public toggle(...flags: E[]): void {
    for (const flag of flags) {
      this.flags ^= (1 << Number(flag));
    }
  }

  // Checks if a flag corresponding to an enum value is set
  public has(flag: E): boolean {
    const f = Number(flag);
    if (f >= 0) {
      return (this.flags & (1 << f)) !== 0;
    } else {
      return (this.flags & (1 << (~f))) === 0;
    }
  }

  // Checks if multiple flags corresponding to enum values are set
  public all(...flags: E[]): boolean {
    for (const flag of flags) {
      if (!this.has(flag)) {
        return false;
      }
    }
    return true;
  }

  public some(...flags: E[]): boolean {
    let val = false;
    for (const flag of flags) {
      val ||= this.has(flag);
    }
    return val;
  }
}

Related Posts

Z-Order Curve

Z-Order Curve

Nemo vel ad consectetur namut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat.

Read More
TypeScript Springs

TypeScript Springs

Nemo vel ad consectetur namut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat.

Read More
TypeScript + Lua

TypeScript + Lua

Nemo vel ad consectetur namut rutrum ex, venenatis sollicitudin urna. Aliquam erat volutpat.

Read More