diff --git a/Project.toml b/Project.toml index 2100ddc..2090570 100644 --- a/Project.toml +++ b/Project.toml @@ -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" diff --git a/README.md b/README.md index d78f1ad..a2e7933 100644 --- a/README.md +++ b/README.md @@ -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 diff --git a/docs/src/clocks.md b/docs/src/clocks.md index d108277..0a7105a 100644 --- a/docs/src/clocks.md +++ b/docs/src/clocks.md @@ -117,6 +117,7 @@ Virtual clocks can be run, stopped or stepped through and thereby used to simula ```@docs run! +@run! incr! resetClock! stop! diff --git a/docs/src/events.md b/docs/src/events.md index 204c5b1..61935f6 100644 --- a/docs/src/events.md +++ b/docs/src/events.md @@ -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: diff --git a/docs/src/history.md b/docs/src/history.md index 277d00b..18892a5 100644 --- a/docs/src/history.md +++ b/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, diff --git a/docs/src/news.md b/docs/src/news.md index 7b40889..0c2e149 100644 --- a/docs/src/news.md +++ b/docs/src/news.md @@ -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 diff --git a/docs/src/processes.md b/docs/src/processes.md index 230cdd2..772f7f1 100644 --- a/docs/src/processes.md +++ b/docs/src/processes.md @@ -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 … @@ -21,7 +21,9 @@ Functions implementing processes create events implicitly by calling `delay!` or ```@docs delay! +@delay wait! +@wait ``` ## Interrupts diff --git a/src/DiscreteEvents.jl b/src/DiscreteEvents.jl index aa578b0..5048698 100644 --- a/src/DiscreteEvents.jl +++ b/src/DiscreteEvents.jl @@ -65,7 +65,7 @@ export Clock, PClock, RTClock, setUnit!, 𝐶, PrcException, Resource, onthread, pseed!, - @process, @event, @run! + @process, @event, @run!, @delay, @wait Random.seed!(123) diff --git a/src/macros.jl b/src/macros.jl index d5e12c8..aa2d103 100644 --- a/src/macros.jl +++ b/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.") @@ -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))) diff --git a/test/runtests.jl b/test/runtests.jl index ab1291a..42bddd9 100644 --- a/test/runtests.jl +++ b/test/runtests.jl @@ -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 diff --git a/test/test_macros.jl b/test/test_macros.jl new file mode 100644 index 0000000..186bb72 --- /dev/null +++ b/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))" + +