class Scheduler {
public:
  cothread_t thread_snes;
  cothread_t thread_cpu;
  cothread_t thread_smp;
  cothread_t thread_ppu;
  cothread_t thread_dsp;

  enum ActiveThread {
    THREAD_CPU,
    THREAD_SMP,
    THREAD_PPU,
    THREAD_DSP,
  };

  struct {
    unsigned cpu_freq;
    unsigned smp_freq;

    ActiveThread active;
    int64 cpuppu;
    int64 cpusmp;
    int64 smpdsp;
  } clock;

  //==========
  //CPU <> PPU
  //==========

  alwaysinline void sync_cpuppu() {
    if(clock.cpuppu < 0) {
      clock.active = THREAD_PPU;
      co_switch(thread_ppu);
    }
  }

  alwaysinline void sync_ppucpu() {
    if(clock.cpuppu >= 0) {
      clock.active = THREAD_CPU;
      co_switch(thread_cpu);
    }
  }

  //==========
  //CPU <> SMP
  //==========

  alwaysinline void sync_cpusmp() {
    if(clock.cpusmp < 0) {
      clock.active = THREAD_SMP;
      co_switch(thread_smp);
    }
  }

  alwaysinline void sync_smpcpu() {
    if(clock.cpusmp >= 0) {
      clock.active = THREAD_CPU;
      co_switch(thread_cpu);
    }
  }

  //==========
  //SMP <> DSP
  //==========

  alwaysinline void sync_smpdsp() {
    if(clock.smpdsp < 0) {
      clock.active = THREAD_DSP;
      co_switch(thread_dsp);
    }
  }

  alwaysinline void sync_dspsmp() {
    if(clock.smpdsp >= 0) {
      clock.active = THREAD_SMP;
      co_switch(thread_smp);
    }
  }

  //======
  //timing
  //======

  alwaysinline void addclocks_cpu(unsigned clocks) {
    clock.cpuppu -= clocks;
    sync_cpuppu();

    clock.cpusmp -= clocks * (uint64)clock.smp_freq;
    if(clock.cpusmp < -(20000 * (int64)24000000)) sync_cpusmp();
  }

  alwaysinline void addclocks_ppu(unsigned clocks) {
    clock.cpuppu += clocks;
    sync_ppucpu();
  }

  alwaysinline void addclocks_smp(unsigned clocks) {
    clock.cpusmp += clocks * (uint64)clock.cpu_freq;
    if(clock.cpusmp > +(20000 * (int64)24000000)) sync_smpcpu();

    clock.smpdsp -= clocks;
    #if !defined(USE_STATE_MACHINE)
    sync_smpdsp();
    #else
    while(clock.smpdsp < 0) dsp.enter();
    #endif
  }

  alwaysinline void addclocks_dsp(unsigned clocks) {
    clock.smpdsp += clocks;
    #if !defined(USE_STATE_MACHINE)
    sync_dspsmp();
    #endif
  }

  void enter();
  void exit();
  void init();

  Scheduler();
};

extern Scheduler scheduler;
