Skip to content

Commit

Permalink
Merge branch 'dev'
Browse files Browse the repository at this point in the history
  • Loading branch information
pbayer committed Dec 20, 2020
2 parents 0d46d65 + 3fc1f8d commit e146b7e
Show file tree
Hide file tree
Showing 11 changed files with 198 additions and 102 deletions.
2 changes: 1 addition & 1 deletion Project.toml
Original file line number Diff line number Diff line change
@@ -1,7 +1,7 @@
name = "Actors"
uuid = "18269d71-cc38-4e02-b8c8-1db30113a6dd"
authors = ["Oliver Schulz", "Paul Bayer"]
version = "0.2.1"
version = "0.2.2"

[deps]
ActorInterfaces = "7e194583-e687-41a0-9133-1c22a29c80af"
Expand Down
6 changes: 6 additions & 0 deletions docs/src/api.md
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,12 @@ Bhv
_ACT
```

You can create your own message types with

```@docs
@msg
```

## Starting Actors, creating links

`Actors.jl` doesn't export its functions to start actors and to create links. Thus other libraries building on it can implement their own actors and links.
Expand Down
60 changes: 31 additions & 29 deletions docs/src/examples/dining_phil.md
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,8 @@ delay(time, msg, cust) = async() do
sleep(time/speedup)
send(cust, msg)
end

@msg Take Taken Busy Put Eat Think
```

The first part of an actor based solution is that each chopstick between the philosophers is an actor. So only one access to a chopstick is possible at a time. And the philosophers will have to communicate with the chopsticks to take them:
Expand All @@ -30,71 +32,71 @@ mutable struct Chopstick
Chopstick() = new(true)
end

function (c::Chopstick)(cust, ::Val{:take})
function (c::Chopstick)(cust, ::Take)
if c.idle
send(cust, self(), Val(:taken))
send(cust, self(), Taken())
c.idle = false
else
send(cust, self(), Val(:busy))
send(cust, self(), Busy())
end
end
(c::Chopstick)(::Val{:put}) = c.idle = true
(c::Chopstick)(::Put) = c.idle = true
```

We have modeled a chopstick actor as a functor with two messages, `:take` and `:put`.

Now the philosophers! We model them with behavior functions representing their state, the respective philosopher as an acquaintance and state transitions with `become`. So a philosopher is a finite state machine:

```julia
function thinking(p::Phil, ::Val{:eat})
send(p.left, self(), Val(:take))
send(p.right, self(), Val(:take))
function thinking(p::Phil, ::Eat)
send(p.left, self(), Take())
send(p.right, self(), Take())
become(hungry, p)
end
function hungry(p::Phil, chop, ::Val{:taken})
function hungry(p::Phil, chop, ::Taken)
chop == p.left ?
become(right_waiting, p) :
become(left_waiting, p)
end
hungry(p::Phil, chop, ::Val{:busy}) = become(denied, p)
function denied(p::Phil, other, ::Val{:taken})
send(other, Val(:put))
hungry(p::Phil, chop, ::Busy) = become(denied, p)
function denied(p::Phil, other, ::Taken)
send(other, Put())
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function denied(p::Phil, chop, ::Val{:busy})
function denied(p::Phil, chop, ::Busy)
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function right_waiting(p::Phil, chop, ::Val{:taken})
function right_waiting(p::Phil, chop, ::Taken)
if chop == p.right
become(eating, p)
p.eaten += te = randn()+eating_time
delay(te, Val(:think), self())
delay(te, Think(), self())
end
end
function right_waiting(p::Phil, chop, ::Val{:busy})
send(p.left, Val(:put))
function right_waiting(p::Phil, chop, ::Busy)
send(p.left, Put())
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function left_waiting(p::Phil, chop, ::Val{:taken})
function left_waiting(p::Phil, chop, ::Taken)
if chop == p.left
become(eating, p)
p.eaten += te = randn()+eating_time
delay(te, Val(:think), self())
delay(te, Think(), self())
end
end
function left_waiting(p::Phil, chop, ::Val{:busy})
send(p.right, Val(:put))
function left_waiting(p::Phil, chop, ::Busy)
send(p.right, Put())
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function eating(p::Phil, ::Val{:think})
send(p.left, Val(:put))
send(p.right, Val(:put))
function eating(p::Phil, ::Think)
send(p.left, Put())
send(p.right, Put())
become(thinking, p)
delay(randn()+thinking_time, Val(:eat), self())
delay(randn()+thinking_time, Eat(), self())
end
```

Expand All @@ -118,7 +120,7 @@ hume = spawn(thinking, Phil(c4,c5,0.0))
plato = spawn(thinking, Phil(c5,c1,0.0))

for p in (descartes, nietzsche, kant, hume, plato)
delay(thinking_time, Val(:eat), p)
delay(thinking_time, Eat(), p)
end
```

Expand Down
44 changes: 23 additions & 21 deletions docs/src/examples/prod_cons.md
Original file line number Diff line number Diff line change
Expand Up @@ -30,31 +30,33 @@ isfull(s::Store) = length(s.items) ≥ s.capacity
Base.isempty(s::Store) = isempty(s.items)
```

We implement the store's behavior as a functor receiving two messages `:put` and `:take`.
We implement the store's behavior as a functor receiving two messages `Put()` and `Take()`.

```julia
function (s::Store)(::Val{:put}, prod, item)
@msg Put Full Done Ok Take Empty Notify

function (s::Store)(::Put, prod, item)
if isfull(s)
send(prod, Val(:full), item)
send(prod, Full(), item)
push!(s.prod, prod)
elseif s.count < maxitems
push!(s.items, item)
s.count += 1
s.count == maxitems ?
send(prod, Val(:done)) :
send(prod, Val(:ok), item)
!isempty(s.cons) && send(popfirst!(s.cons), Val(:notify))
send(prod, Done()) :
send(prod, Ok(), item)
!isempty(s.cons) && send(popfirst!(s.cons), Notify())
else
send(prod, Val(:done))
send(prod, Done())
end
end
function (s::Store)(::Val{:take}, cons)
function (s::Store)(::Take, cons)
if isempty(s)
send(cons, Val(:empty))
send(cons, Empty())
push!(s.cons, cons)
else
send(cons, popfirst!(s.items))
!isempty(s.prod) && send(popfirst!(s.prod), Val(:notify))
!isempty(s.prod) && send(popfirst!(s.prod), Notify())
end
end
```
Expand Down Expand Up @@ -83,47 +85,47 @@ function prod_start(p::Prod, start)
end
function producing(p::Prod, item)
sleep(rand())
send(p.store, Val(:put), self(), item)
send(p.store, Put(), self(), item)
end
function producing(p::Prod, ::Val{:ok}, item)
function producing(p::Prod, ::Ok, item)
send(prn, "producer $(p.name) delivered item $item")
send(self(), item+1)
end
function producing(p::Prod, ::Val{:full}, item)
function producing(p::Prod, ::Full, item)
send(prn, "producer $(p.name) stalled with item $item")
become(stalled, p, item)
end
function producing(p::Prod, ::Val{:done})
function producing(p::Prod, ::Done)
send(prn, "producer $(p.name) done")
stop()
end
function stalled(p::Prod, item, ::Val{:notify})
send(p.store, Val(:put), self(), item)
function stalled(p::Prod, item, ::Notify)
send(p.store, Put(), self(), item)
become(producing, p)
end

function cons_start(c::Cons)
become(buying, c)
send(c.store, Val(:take), self())
send(c.store, Take(), self())
send(prn, "consumer $(c.name) started")
end
function buying(c::Cons, item)
become(consuming)
send(self(), c)
send(prn, "consumer $(c.name) got item $item")
end
function buying(c::Cons, ::Val{:empty})
function buying(c::Cons, ::Empty)
become(waiting, c)
send(prn, "consumer $(c.name) found store empty")
end
function consuming(c)
sleep(rand())
become(buying, c)
send(c.store, Val(:take), self())
send(c.store, Take(), self())
end
function waiting(c::Cons, ::Val{:notify})
function waiting(c::Cons, ::Notify)
become(buying, c)
send(c.store, Val(:take), self())
send(c.store, Take(), self())
end
```

Expand Down
4 changes: 4 additions & 0 deletions docs/src/infrastructure.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,10 @@ CurrentModule = Actors
- actor tasks and
- actor registry.

## User Defined Messages

Often you want to define your own message types. For defining empty messages you can use the [`@msg`](@ref) macro.

## Actor Tasks

Actor tasks execute one computation, mostly without communicating with other actors. They can be used to compute values asynchronously.
Expand Down
60 changes: 31 additions & 29 deletions examples/dining_phil.jl
Original file line number Diff line number Diff line change
Expand Up @@ -26,65 +26,67 @@ delay(time, msg, cust) = async() do
send(cust, msg)
end

function (c::Chopstick)(cust, ::Val{:take})
@msg Take Taken Busy Put Eat Think

function (c::Chopstick)(cust, ::Take)
if c.idle
send(cust, self(), Val(:taken))
send(cust, self(), Taken())
c.idle = false
else
send(cust, self(), Val(:busy))
send(cust, self(), Busy())
end
end
(c::Chopstick)(::Val{:put}) = c.idle = true
(c::Chopstick)(::Put) = c.idle = true

function thinking(p::Phil, ::Val{:eat})
send(p.left, self(), Val(:take))
send(p.right, self(), Val(:take))
function thinking(p::Phil, ::Eat)
send(p.left, self(), Take())
send(p.right, self(), Take())
become(hungry, p)
end
function hungry(p::Phil, chop, ::Val{:taken})
function hungry(p::Phil, chop, ::Taken)
chop == p.left ?
become(right_waiting, p) :
become(left_waiting, p)
end
hungry(p::Phil, chop, ::Val{:busy}) = become(denied, p)
function denied(p::Phil, other, ::Val{:taken})
send(other, Val(:put))
hungry(p::Phil, chop, ::Busy) = become(denied, p)
function denied(p::Phil, other, ::Taken)
send(other, Put())
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function denied(p::Phil, chop, ::Val{:busy})
function denied(p::Phil, chop, ::Busy)
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function right_waiting(p::Phil, chop, ::Val{:taken})
function right_waiting(p::Phil, chop, ::Taken)
if chop == p.right
become(eating, p)
p.eaten += te = randn()+eating_time
delay(te, Val(:think), self())
delay(te, Think(), self())
end
end
function right_waiting(p::Phil, chop, ::Val{:busy})
send(p.left, Val(:put))
function right_waiting(p::Phil, chop, ::Busy)
send(p.left, Put())
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function left_waiting(p::Phil, chop, ::Val{:taken})
function left_waiting(p::Phil, chop, ::Taken)
if chop == p.left
become(eating, p)
p.eaten += te = randn()+eating_time
delay(te, Val(:think), self())
delay(te, Think(), self())
end
end
function left_waiting(p::Phil, chop, ::Val{:busy})
send(p.right, Val(:put))
function left_waiting(p::Phil, chop, ::Busy)
send(p.right, Put())
become(thinking, p)
send(self(), Val(:eat))
send(self(), Eat())
end
function eating(p::Phil, ::Val{:think})
send(p.left, Val(:put))
send(p.right, Val(:put))
function eating(p::Phil, ::Think)
send(p.left, Put())
send(p.right, Put())
become(thinking, p)
delay(randn()+thinking_time, Val(:eat), self())
delay(randn()+thinking_time, Eat(), self())
end

eaten(phils...) = Tuple(round(Int, query(p, :bhv).a[1].eaten) for p in phils)
Expand All @@ -102,7 +104,7 @@ hume = spawn(thinking, Phil(c4,c5,0.0))
plato = spawn(thinking, Phil(c5,c1,0.0))

for p in (descartes, nietzsche, kant, hume, plato)
delay(thinking_time, Val(:eat), p)
delay(thinking_time, Eat(), p)
end

for i in 1:5
Expand Down
Loading

0 comments on commit e146b7e

Please sign in to comment.