Internals

The following types are handled internally by DiscreteEvents.jl, but maybe necessary for analyzing and debugging clocks and event schedules.

Events

DiscreteEvents.DiscreteEventType

`DiscreteEvent{T<:Action, X} <: AbstractEvent A discrete event is an Action to be executed at an event time.

Arguments, fields

  • ex::T: a function or an expression or a tuple of them,
  • t::Float64: event time,
  • Δt::X: repeat interval (Float64, Distribution or Nothing),
  • n::Int: number of repeats.
DiscreteEvents.DiscreteCondType
DiscreteCond{S<:Action, T<:Action} <: AbstractEvent

A condition to be evaluated repeatedly with expressions or functions to be executed if conditions are met.

Arguments, fields

  • cond::S: a conditional function or an expression or a tuple of them (conditions must evaluate to Bool),
  • ex::T: a function or an expression or a tuple of them to be executed if conditions are met,
DiscreteEvents.SampleType
Sample{T<:Action} <: AbstractEvent

Sampling actions are executed at sampling time.

Arguments, fields

  • ex<:Action: an Action to be executed at sample time.

Clocks

There is an abstract type for clocks and an active clock, used for controlling parallel clocks.

DiscreteEvents.ActiveClockType
ActiveClock{E <: ClockEvent} <: AbstractClock

An active clock is a wrapper around a local Clock on a parallel thread. It is operated by a thread local task. The master clock on thread 1 communicates with it through messages over its channels.

Fields

  • clock::Clock: the thread specific local clock,
  • master::Ref{Clock}: a pointer to the master clock (on thread 1),
  • forth::Channel{E}: the command channel from master,
  • back::Channel{E}: the response channel to master,
  • id::Int: the clocks id/thread number,
  • task::Task: the active clock`s task.
Don't setup an active clock explicitly!

It is done implicitly with PClock or by fork!ing a Clock to other available threads.

On a parallel thread tasks can access their local clock with pclock(clk). Then they can schedule 'thread local' events, delay! or wait! on it.

Don't share active clocks between threads!

In multithreading we communicate over channels and don't want to share variables between threads. A user can still access active clocks for diagnostic purposes.

DiscreteEvents.localClockFunction
localClock(c::Clock)

Create a thread local clock with an undefined reference to an active clock which inherits parameters of the master clock c.

Schedule and ClockChannel are two important Clock substructures:

DiscreteEvents.ScheduleType
Schedule()

A schedule contains events, conditional events and sampling functions to be executed or evaluated on the clock's time line.

Fields

  • events::PriorityQueue{DiscreteEvent,Float64}: scheduled events,
  • cevents::Array{DiscreteCond,1}: conditional events to evaluate at each tick,
  • samples::Array{Sample,1}: sampling expressions to evaluate at each tick,
DiscreteEvents.ClockChannelType
ClockChannel{T <: ClockEvent}

Provide a message channel to an active clock or a real time clock.

Fields

  • ref::Ref{Task}: a reference to an active clock task, useful for diagnosis,
  • forth::Channel{T}: a communication channel to an active clock,
  • back::Channel{T}: response channel from the active clock,
  • thread::Int: the thread id of the active clock,
  • done::Bool: flag indicating if the active clock has completed its cycle,
  • load::Int: internal flag.

Clock concurrency

If a task after activation by the clock gives control back to the Julia scheduler (e.g. by reading from a channel or by doing an IO-operation), it enqueues for its next schedule behind the clock. The clock may then increment time to $t_{i+1}$ before the task can finish its job at current event time $t_i$.

There are several ways to solve this problem:

  1. The clock does a 2ⁿᵈ yield() after invoking a task and enqueues again at the end of the scheduling queue. This is implemented for delay! and wait! of processes and should be enough for most those cases.
  2. Actors push! their message channel to the clock.channels vector and the clock will only proceed to the next event if all registered channels are empty [1].
  3. Tasks use now! to let the (master) clock do IO-operations for them. They can also print via the clock.

Error handling and diagnosis

  • 1In YAActL you can register! to a Vector{Channel}. To register actors is also useful for diagnosis.