module concurrency.bitfield;

import core.atomic : atomicOp, atomicLoad, MemoryOrder;

struct Guard(Flags) {
  private SharedBitField!(Flags)* obj;
  size_t oldState, newState;
  ~this() {
    release();
  }
  void release(size_t sub = 0) {
    if (obj !is null) {
      // TODO: want to use atomicFetchSub but (proper) support is only recent
      // obj.store.atomicFetchSub!(MemoryOrder.rel)(sub | Flags.locked);
      obj.store.atomicOp!"-="(sub | Flags.locked);
    }
    obj = null;
  }
  bool was(Flags flags) {
    return (oldState & flags) == flags;
  }
}

shared struct SharedBitField(Flags) {
  static assert(__traits(compiles, Flags.locked), "Must has a non-zero 'locked' flag");
  static assert(Flags.locked != 0, "Must has a non-zero 'locked' flag");
  private shared size_t store;
  Guard!Flags lock(size_t or = 0, size_t add = 0, size_t sub = 0) return scope @safe @nogc nothrow {
    return Guard!Flags(&this, update(Flags.locked | or, add, sub).expand);
  }
  auto update(size_t or, size_t add = 0, size_t sub = 0) nothrow {
    import concurrency.utils : spin_yield, casWeak;
    import std.typecons : tuple;
    size_t oldState, newState;
    do {
      goto load_state;
      do {
        spin_yield();
      load_state:
        oldState = store.atomicLoad!(MemoryOrder.acq);
      } while ((oldState & Flags.locked) > 0);
      newState = (oldState + add - sub) | or;
    } while (!casWeak!(MemoryOrder.acq, MemoryOrder.acq)(&store, oldState, newState));
    return tuple!("oldState", "newState")(oldState, newState);
  }
  auto add(size_t add) nothrow {
    store.atomicOp!"+="(add);
  }
  size_t load(MemoryOrder ms)() {
    return store.atomicLoad!ms;
  }
}