Internals
The following types are handled internally by DiscreteEvents.jl, but maybe necessary for analyzing and debugging clocks and event schedules.
Events
DiscreteEvents.AbstractEvent — TypeSupertype for events.
DiscreteEvents.DiscreteEvent — Type`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.DiscreteCond — TypeDiscreteCond{S<:Action, T<:Action} <: AbstractEventA 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 toBool),ex::T: a function or an expression or a tuple of them to be executed if conditions are met,
DiscreteEvents.Sample — TypeSample{T<:Action} <: AbstractEventSampling actions are executed at sampling time.
Arguments, fields
ex<:Action: anActionto be executed at sample time.
Clocks
There is an abstract type for clocks and an active clock, used for controlling parallel clocks.
DiscreteEvents.AbstractClock — TypeSupertype for clocks.
DiscreteEvents.GlobalClock — TypeGlobal clock with vector of active clocks.
DiscreteEvents.ActiveClock — TypeActiveClock{E <: ClockEvent} <: AbstractClockAn 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.
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.
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.LocalClock — TypeLocal clock with reference to a wrapping active clock.
DiscreteEvents.localClock — FunctionlocalClock(c::Clock)Create a thread local clock with an undefined reference to an active clock which inherits parameters of the master clock c.
An example on active clocks:
julia> using DiscreteEvents
julia> clk = Clock() # create a clock
Clock 1: state=:idle, t=0.0, Δt=0.01, prc:0
scheduled ev:0, cev:0, sampl:0
julia> fork!(clk) # fork it to parallel threads
julia> clk # now you see parallel active clocks
Clock 1 (+1): state=:idle, t=0.0, Δt=0.01, prc:0
scheduled ev:0, cev:0, sampl:0
julia> clk = PClock() # create a parallel clock structure
Clock 1 (+1): state=:idle, t=0.0, Δt=0.01, prc:0
scheduled ev:0, cev:0, sampl:0
julia> ac2 = pclock(clk, 2) # get access to the active clock on thread 2
Active clock 2: state=:idle, t=0.0, Δt=0.01, prc:0
scheduled ev:0, cev:0, sampl:0
julia> ac2.clock # access the parallel clock 2
Clock 2: state=:idle, t=0.0, Δt=0.01, prc:0
scheduled ev:0, cev:0, sampl:0Schedule and ClockChannel are two important Clock substructures:
DiscreteEvents.Schedule — TypeSchedule()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.ClockChannel — TypeClockChannel{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:
- The clock does a 2ⁿᵈ
yield()after invoking a task and enqueues again at the end of the scheduling queue. This is implemented fordelay!andwait!of processes and should be enough for most those cases. - Actors
push!their message channel to theclock.channelsvector and the clock will only proceed to the next event if all registered channels are empty [1]. - Tasks use
now!to let the (master) clock do IO-operations for them. They can alsoprintvia the clock.
Error handling and diagnosis
DiscreteEvents.prettyClock — FunctionprettyClock(on::Bool)Switch pritty printing for clocks on and off.