docs
Systems
Execution
SchedulerSystem
Architecture and Design

Design patterns in the code

  • Static factories for messages. SchedulerCommandFactory and SchedulerEventFactory build Command and Event objects for the Scheduler domain and send or raise them through BusUtility. Callers do not construct scheduler messages directly.
  • Flow controller. SchedulerFlowController inherits from FlowController. It owns the Scheduler domain on the command bus and forwards commands to a SchedulerManager.
  • Abstract base with an abstract hook. SchedulerManager implements the shared scheduling logic. The ProcessEvent method is abstract, so each project supplies its own event handling.
  • Typed keys. Job and subject values travel in Params under SchedulerKeys, which are ParamKey instances built from SchedulerKeyNames.
  • Static helpers. SchedulerService and SchedulerUtility hold stateless logic for validation, due-job detection, completion, and job creation.
  • Static lookup. ServiceLocator (a dependency) provides the bus registry. SchedulerFlowController reads the EventBus through it.

Connections to dependencies

  • Contracts. Domain.Scheduler identifies the domain. CoreParamKeys.StringValue is used by one factory method. The scheduler's event verbs live in the Contracts namespace.
  • Params. Params and ParamKey<T> carry the job and subject. Each command and event owns its own Params instance.
  • FlowController. The base class registers HandleCommand on the CommandBus for the Scheduler domain during Init.
  • Bus. BusUtility.Send delivers commands. BusUtility.Raise delivers events. SchedulerFlowController subscribes to Data-domain events on the EventBus.
  • Update. SchedulerFlowController.Init calls UpdateCommandFactory.RegisterUpdatable, which places the SchedulerManager in the update system. Each frame, the update system calls OnUpdate, which calls Tick.
  • Application. ApplicationConstants.PausedGameSpeed is used to skip ticks when Time.timeScale is zero or below.

Key concepts

  • Job. The scheduler's record for one scheduled item (SchedulerConstants.Job). It holds an ID, a delay, an interval, a repeat count, the Config, and the Params it was scheduled with.
  • Config. A struct with Delay, Interval, RepeatCount, and optional Event and Command fields. The scheduler reads only the first three.
  • IScheduledJob. The interface a job payload implements. It exposes JobMetaData, Execute, and SetJobID.
  • Interval and repeats. An interval greater than zero makes a job repeat. An interval of zero makes it run once, regardless of RepeatCount.
  • Pause. A flag in SchedulerManager. While it is set, Tick returns without changing any job.

Data and event flow

Scheduling:

  1. A caller creates a job and calls a SchedulerCommandFactory method.
  2. The factory builds a Command for the Scheduler domain and sends it through BusUtility.Send.
  3. The CommandBus calls SchedulerFlowController.HandleCommand, which calls SchedulerManager.ProcessCommand.
  4. HandleSchedule checks the subject, reads the job's Config, and calls SchedulerService.ValidateSchedulerStruct.
  5. If valid, SchedulerUtility assigns the next ID and creates a SchedulerConstants.Job. The job is added to the jobs dictionary.

Ticking:

  1. The update system calls SchedulerManager.OnUpdate, which calls Tick.
  2. Tick returns early if paused, if time scale is at or below the paused speed, or if there are no jobs.
  3. SchedulerService.FindDueJobs takes a snapshot of the job IDs, subtracts Time.deltaTime from each job's delay, and collects the IDs whose delay is at or below zero.
  4. For each due job, ExecuteJob decrements the repeat count, calls SetJobID, and then calls Execute.
  5. If Execute returns true, SchedulerService.HandleJobCompletion either removes the job or resets its delay to its interval.

Pause and resume:

  1. A pause or resume command sets or clears the paused flag in SchedulerManager.
  2. SchedulerEventFactory raises Paused or Resumed on the EventBus in the Scheduler domain.

Incoming events:

  1. SchedulerFlowController subscribes to the Data domain on the EventBus.
  2. Each Data event is passed to SchedulerManager.ProcessEvent, which the project subclass implements.

Cancel:

  1. CancelScheduledJobs sends a Cancel command with the Job subject.
  2. HandleCancel clears the jobs dictionary.

Timing rules

  • Delay is counted down by Time.deltaTime on each tick that actually runs. A job whose delay is zero or less is due on that tick.
  • A job scheduled with a delay of zero runs on the first tick after it is scheduled.
  • A repeating job with an interval greater than zero has its delay reset to the interval after each successful run.
  • Repeat count is decremented before execution. A job is removed when its count falls below the threshold of 1, but only after a successful Execute.

Failed execution: if Execute returns false, the repeat count is still decremented, the delay is not reset, and the job is not removed. Because the delay stays at or below zero, the job is due again on the next tick. The repeat count can then go below the threshold without removing the job, since removal only happens after a successful Execute.

Thread safety

The scheduler runs on the Unity main thread, so schedule, pause, resume and cancel jobs from the main thread only.