Skip to content

Postpone System

Sam edited this page Jun 13, 2023 · 21 revisions

System overview

Auto Dark Mode has two main components. The first part is the postpone manager which is responsible for delaying theme switches. The second part is a theme approach mechanism that can be used to listen whether a theme switch is about to happen.

Postpone Manager

The postpone manager in Auto Dark Mode delays and pauses theme switches. It will temporarily overpower Governors, which are responsible for configuring light/dark states according to an input source (i.e system time, or windows night light) and requesting theme switches automatically.

Usually modules will want to make use of the postpone system to modify the theme switch behavior, although system events and user input (UI, keybinds, etc.) are also uses cases.

Essentially, the postpone system is a list which you can add to and remove from. If at least one element is present in the list, theme switching will be postponed. Once the list is empty, theme switching (governors) will be re-enabled.

The postpone manager is available within the Auto Dark Mode GlobalState.

You can get an instance like this:

private GlobalState State { get; } = GlobalState.Instance();

If you would like to add a postpone, you can do it like this

// isUserClearable determines whether a user is able to clear the postpone by pressing the resume button in the UI
// or by sending a clear postpone command via the CLI.
// Name is simply a string that is used to identify the postpone queue object. 
// Names are unique.
string Name = "MyPostponeReason";
State.PostponeManager.Add(new(Name, isUserClearable: true));

You can remove postpones by passing the name into the respective method.

/// Removes an existing blocking reason from the postpone queue and invokes registered 
/// callbacks if the queue is running empty
/// Returns True if removal was successful; false otherwise</returns>
State.PostponeManager.Remove(Name);

It is also possible to register callback modules here in case they need to be fired instantly after the postpone queue clears up.

Example within the EnableHook and DisableHook of a Module.

public override void EnableHook() 
{
   State.PostponeManager.RegisterCallbackModule(this);
}

public override void DisableHook() 
{
   State.PostponeManager.RegisterCallbackModule(this);
}

Theme approach listener with IAutoDarkModeModule

Auto Dark Mode provides a property to check if a theme switch is about to happen. For time based governors, this will happen one timer tick before a switch is triggered. The night light governor, if a postpone module is registered, will instead wait for one timer tick after the state switches. This happens because we can't anticipate when the night light state is updated.

Why do we need this?

Modules are on a fixed timer that will periodically poll them by invoking its Fire() method. If a module postpones in Auto Dark Mode, it should not be performing costly logic operations unnecessarily. For that reason we only want them to start operating shortly before a switch occurs, and keep running UNTIL their condition has been cleared. Then they will return to sleep again.

How?

You can check this variable in your module and start performing higher-cost logic to determine whether a switch is supposed to be postponed or not.

private bool AllowMonitoring { get; set; }
private bool MonitoringActive { get; set; }
private GlobalState State { get; } = GlobalState.Instance();

public override void Fire() 
{
   // this is a basic set/reset guard similar to a SR flip-flop
   if (!State.SwitchApproach.ThemeSwitchApproaching && !MonitoringActive)
   {
      AllowMonitoring = true;
   }
   // if a theme switch is approaching and the module is not monitoring yet, we need to enable the module
   if (State.SwitchApproach.ThemeSwitchApproaching && !MonitoringActive && AllowMonitoring)
   {
      Logger.Info($"start monitoring whatever I need");
      // Name is already available in a module
      State.PostponeManager.Add(new(Name, isUserClearable: true));
   
      // monitoring can only be used again once the reset condition has been met
      AllowMonitoring = false;
      MonitoringActive = true;
   }

   if (MonitoringActive)
   {
      // do my costly stuff
      // if condition has been met, set MonitoringActive to false
      State.PostponeManager.Remove(new(Name, isUserClearable: true));
   }
}

In addition, you must register modules that should postpone a switch. The registration ensures that they are called before the governor invokes a theme switch.

You may register and unregister the switch dependency like this

public override void EnableHook()
{
   State.SwitchApproach.AddDependency(this);
}
public override void DisableHook()
{
   State.SwitchApproach.RemoveDependency(this);
}