Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
pbayer committed Sep 15, 2020
2 parents dd929ce + db7a24c commit 079cc12
Show file tree
Hide file tree
Showing 11 changed files with 131 additions and 30 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Expand Up @@ -12,7 +12,7 @@ Unitful = "1986cc42-f94f-5a68-af5c-568840ba703d"

[compat]
DataStructures = "0.17"
Distributions = "^0.22"
Distributions = "0.22, 0.23"
Unitful = "1"
julia = "1"

Expand Down
2 changes: 1 addition & 1 deletion README.md
Expand Up @@ -22,7 +22,7 @@ using DiscreteEvents, Printf, Distributions, Random
function serve(clk::Clock, id::Int, input::Channel, output::Channel, X::Distribution)
job = take!(input)
print(clk, @sprintf("%6.3f: server %d serving customer %d\n", tau(clk), id, job))
@delay! clk, X
@delay! clk X
print(clk, @sprintf("%6.3f: server %d finished serving %d\n", tau(clk), id, job))
put!(output, job)
end
Expand Down
1 change: 1 addition & 0 deletions docs/src/clocks.md
Expand Up @@ -117,6 +117,7 @@ Virtual clocks can be run, stopped or stepped through and thereby used to simula

```@docs
run!
@run!
incr!
resetClock!
stop!
Expand Down
8 changes: 8 additions & 0 deletions docs/src/events.md
Expand Up @@ -65,6 +65,14 @@ sample_time!
periodic!
```

## Macros

Functions can be scheduled to the clock as timed and conditional events with the `@event` macro. This wraps a given function into a `fun` closure and calls `event!` on it.

```@docs
@event
```

## Events and Variables

Actions often depend on data or modify it. The data may change between the definition of an action and its later execution. If an action uses a *mutable variable* like an array or a mutable struct, it gets current data at event time and it is fast. If the action modifies the data, this is the best way to do it:
Expand Down
4 changes: 4 additions & 0 deletions docs/src/history.md
@@ -1,5 +1,9 @@
# Version history

```@meta
CurrentModule = DiscreteEvents
```

## v0.3.0

v0.3.0 was a significant improvement over 0.2.0 with a name change,
Expand Down
15 changes: 9 additions & 6 deletions docs/src/news.md
Expand Up @@ -4,13 +4,16 @@
CurrentModule = DiscreteEvents
```

A few days after the release of v0.3.0 Hector Perez created some macros facilitating the use of `DiscreteEvents` for most use cases:
Few days after the release of v0.3.0 Hector Perez contributed macros to be used for common cases:

- [`@event`](@ref):
- [`@process`](@ref):
- [`@delay`](@ref):
- [`@wait`](@ref):
- [`@run`](@ref):
- [`@event`](@ref): create an event, wraps [`fun`](@ref) and [`event!`](@ref) into one call,
- [`@process`](@ref): create a process, wraps [`Prc`](@ref) and [`process!`](@ref) into one call,
- [`@wait`](@ref): simplified call of [`wait!`](@ref),

The following macros provide syntactic sugar to existing functions:

- [`@delay`](@ref): calls [`delay!`](@ref),
- [`@run!`](@ref): calls [`run!`](@ref).

## Earlier releases

Expand Down
6 changes: 4 additions & 2 deletions docs/src/processes.md
Expand Up @@ -7,12 +7,12 @@ Processes are *typical event sequences* running as asynchronous tasks.
To setup a process, you

1. implement it in a function taking a [`Clock`](@ref) variable as its first argument,
2. specify the process id and its arguments with `Prc`,
3. start it as an asynchronous task and register it to a clock with `process!`.
2. wrap it into `Prc` and start it as an asynchronous task with `process!`. The `@process` macro does this in one call.

```@docs
Prc
process!
@process
```

## Delay and Wait …
Expand All @@ -21,7 +21,9 @@ Functions implementing processes create events implicitly by calling `delay!` or

```@docs
delay!
@delay
wait!
@wait
```

## Interrupts
Expand Down
2 changes: 1 addition & 1 deletion src/DiscreteEvents.jl
Expand Up @@ -65,7 +65,7 @@ export Clock, PClock, RTClock, setUnit!, 𝐶,
PrcException,
Resource,
onthread, pseed!,
@process, @event, @run!
@process, @event, @run!, @delay, @wait


Random.seed!(123)
Expand Down
88 changes: 69 additions & 19 deletions src/macros.jl
@@ -1,9 +1,26 @@
#
# This file is part of the DiscreteEvents.jl Julia package, MIT license
#
# Hector Perez, Paul Bayer, 2020
#
# This is a Julia package for discrete event simulation
#

"""
@proceses
@process f(arg...) [cycles]
Create a process from a function `f(arg...)`.
Wrap a function and its arguments in a [`Prc`](@ref) and start it with
[`process!`](@ref).
Create a process from a function.
# Arguments
- `f`: a function,
- `arg...`: arguments, the first argument must be an AbstractClock,
- `cycles::Int`: the number of cycles, `f` should run.
Note: the first arg to the function being passed must be an AbstractClock
# Returns
- an `Int` process id.
"""
macro process(expr, args...)
expr.head != :call && error("Expression is not a function call.")
Expand All @@ -20,50 +37,83 @@ macro process(expr, args...)
end

"""
@event
@event f(arg...) T t [n]
Schedule an event.
Schedule a function `f(arg...)` as an event to a clock.
Note: if 3 arguments are passed after the function being called,
the third one is assumed to be the keyword argument `n`.
# Arguments
- `f`: function to be executed at event time,
- `arg...`: its arguments, the first argument must be a clock,
- `T`: a [`Timing`](@ref) (at, after, every),
- `n`: passed as keyword `n` to `event!`.
"""
macro event(expr, args...)
expr.head != :call && error("Expression is not a function call.")
expr.head != :call && error("1st term is not a function call.")
f = expr.args[1] #extract function passed
c = expr.args[2] #first function arg must be an AbstractClock
fargs = expr.args[2:end] #extract other function args
ex = :(fun($f, $(fargs...))) #create Action
if length(args) == 3
esc(:(event!($c, $ex, $(args[1:2]...), n = $args[3]))) #execute event!
esc(:(event!($c, $ex, $(args[1:2]...), n = $(args[3])))) #execute event!
else
esc(:(event!($c, $ex, $(args...)))) #execute event!
end
end

"""
@delay
@event f(farg...) c(carg...)
Schedule a function `f(farg...)` as a conditional event to a clock.
# Arguments
- `f`: function to be executed at event time,
- `farg...`: its arguments, the first argument must be a clock,
- `c`: function to be evaluated at the clock's sample rate, if it
returns true, the event is triggered,
- `carg...`: arguments to c.
"""
macro event(expr, cond)
expr.head != :call && error("1st term is not a function call.")
f1 = expr.args[1]
c = expr.args[2]
f1args = expr.args[2:end]
ex1 = :(fun($f1, $(f1args...)))
cond.head != :call && error("2nd term is not a function call.")
f2 = cond.args[1]
f2args = cond.args[2:end]
ex2 = :(fun($f2, $(f2args...)))
esc(:(event!($c, $ex1, $ex2)))
end

Delay a process.
"""
```
@delay clk Δt
@delay clk until t
```
Delay a process on a clock `clk` for a time interval `Δt` or until
a time `t`.
"""
macro delay(clk, delay...)
esc(:(delay!($clk, $(delay...))))
end

"""
@wait
@wait clk f(arg...)
Delay a process until a condition has been met.
Conditionally wait on a clock `clk` until `f(arg...)` returns true.
"""
macro wait(clk, cond)
exc(:(wait!($clk, $cond)))
macro wait(clk, expr)
expr.head != :call && error("2nd term is not a function call.")
f = expr.args[1]
fargs = expr.args[2:end]
ex = :(fun($f, $(fargs...)))
esc(:(wait!($clk, $ex)))
end

"""
@run!
Run a simulation for a given duration.
@run! clk t
Takes two arguments: clock and duration.
Run a clock `clk` for a duration `t`.
"""
macro run!(clk, duration)
esc(:(run!($clk, $duration)))
Expand Down
1 change: 1 addition & 0 deletions test/runtests.jl
Expand Up @@ -18,6 +18,7 @@ using DiscreteEvents, Test, SafeTestsets, .Threads
@safetestset "Processes" begin include("test_process.jl") end
@safetestset "Resources" begin include("test_resources.jl") end
@safetestset "Utilities" begin include("test_utils.jl") end
@safetestset "Macros" begin include("test_macros.jl") end

if (VERSION v"1.3") && (nthreads() > 1)
@safetestset "timer.jl" begin include("test_timer.jl") end
Expand Down
32 changes: 32 additions & 0 deletions test/test_macros.jl
@@ -0,0 +1,32 @@
#
# This file is part of the DiscreteEvents.jl Julia package, MIT license
#
# Paul Bayer, 2020
#
# This is a Julia package for discrete event simulation
#
using DiscreteEvents

a = 1
b = 1
f(x, y) = x+y
g(x, y) = x == y
h(clk, x, y) = clk.time + x + y

clk = Clock()

@test repr(@macroexpand @event h(clk, a, b) at a) == ":(event!(clk, fun(h, clk, a, b), at, a))"
if VERSION > v"1.5"
@test repr(@macroexpand @event h(clk, a, b) every a 10) == ":(event!(clk, fun(h, clk, a, b), every, a, n = 10))"
end
@test repr(@macroexpand @event h(clk, a, b) g(x, y)) == ":(event!(clk, fun(h, clk, a, b), fun(g, x, y)))"
@test repr(@macroexpand @event h(clk, a, b) a==1) == ":(event!(clk, fun(h, clk, a, b), fun(==, a, 1)))"

@test repr(@macroexpand @process h(clk, a, b)) == ":(process!(clk, Prc(h, a, b)))"
@test repr(@macroexpand @delay clk a) == ":(delay!(clk, a))"

@test repr(@macroexpand @wait clk g(a,b)) == ":(wait!(clk, fun(g, a, b)))"

@test repr(@macroexpand @run! clk a) == ":(run!(clk, a))"


0 comments on commit 079cc12

Please sign in to comment.