-
Notifications
You must be signed in to change notification settings - Fork 1
StashAid
Text compliant with Leucine version 0.4.3
The messages that enter the mailbox appear in (pseudo) random order. The only situation where there is certainty about the order, is when two messages originate from the same actor and are send directly to the mailbox. They will keep their order.
However, in the processing a certain order may be required,
or it may only be possible to process a message when in a
certain (future) state. So we want to be able to put away
a message for later processing. That is where StashAid
is for.
When the StashAid is mixed in, a new object Stash
object appears
that holds the following methods:
/**
* Store a letter and sender manually on the stash. With this method, you may replace one
* letter with an other, or spoof the sender, and reprocess later. If the actor was asked to
* finish, store will still work, since the letter was from before that request. */
def store[Sender <: Accept](letter: Letter[Sender], sender: Sender): Unit
/**
* Automatically stores the current letter (and sender) that is processed on the stash. If the
* actor was asked to finish, store will still work, since the letter was from before that request. */
def store(): Unit
/** Clear the stash instantly. */
def clear(): Unit
/**
* Flush the stash to the mailbox. This is usually the last instruction before you switch to a
* new state to handle the stashed messages once more. If the actor was asked to finish,
* flush will still work, since the stored letters were from before that request. */
def flush(): Unit
/** See how many letters are on the stash. This is fast: O(1). */
def size: Int
/** See if there are any letters on the stash. */
def isEmpty: Boolean
The order in which the messages are stash is preserved. And at a flush()
, the stashed
messages are put in front of the current mailbox. This implies that after flush()
the
next message to be expected is the one that was stashed first.
The actor is implemented making use of a two lists, like the Scala Queue
but tailor made
for this needs. Its size is not limited other than by machine limits.
The signature of the first store(...)
method depends on the actor in which it is used.
We have:
-
WideActor
=>def store(letter: Letter, sender: Actor): Unit
-
AcceptActor
=>def store(letter: Letter): Unit
-
SelectActor
=>def store(letter: Letter, sender: Sender): Unit
-
RestrictActor
=>def store[Sender <: Accept](letter: Letter[Sender], sender: Sender): Unit
To illustrate its function, we define an actor that behaves like a programmable stack for integers. The specialty about this stack is that it stores odd numbers directly whereas if keeps the even numbers that arrive apart, until the Stack is Released. First the even numbers are added to the stack, and from that moment on, numbers are stored in the order that they arrive.
The stack itself is contained in the State
(so leave out Stateless
here), as
well as the block
boolean, which keeps even numbers apart.
Its function becomes clear when looking at the letters:
object Stack extends AcceptDefine :
sealed trait Letter extends Actor.Letter[Actor]
/* Write one integer to the stack. */
case class Write(value: Int) extends Letter
/* Release the blocking of even numbers */
case object Release extends Letter
/* Print and clean the current Stack. */
case object Spool extends Letter
/* The State contains the Stack and the block situation.*/
case class State(values: List[Int], block: Boolean) extends Actor.State :
def show: String = values.mkString(",")
/* We must always specify how to start. */
val initial = Stack.State(Nil,true)
To implement these functions we define:
class Stack extends AcceptActor(Stack), StashAid :
def receive(letter: Letter): Receive = (state: State) => letter match
/* Handle the case a new number arrives. */
case Stack.Write(value) =>
/* If we are blocked and the number is even ... */
if state.block && value%2==0
/* ... store away this message*/
then { Stash.store(); state }
/* otherwise add it to the Stack .*/
else state.copy(value::state.values)
/* The block on the stash is released */
case Stack.Release =>
/* reprocess the stashed messages. */
Stash.flush();
/* Unblock the Stack */
state.copy(block=false)
/* We want to know the contents */
case Stack.Spool =>
/* Show what we have got. */
println(s"Stack content: ${state.show}")
/* Return to the initial Stack State */
Stack.initial
And to test we manipulate a sequence of numbers (1..12) as follows:
/* Main entry point for the demo. */
object Main :
/* We must provide a default sender. */
given Actor.Anonymous = Actor.Anonymous
/* Create the Stack. */
private val stack = new Stack
def main(args: Array[String]): Unit =
( 1 to 8).foreach(i => stack ! Stack.Write(i))
stack ! Stack.Release
( 9 to 12).foreach(i => stack ! Stack.Write(i))
stack ! Stack.Spool
stack.stop(Actor.Stop.Finish)
Thread.sleep(200)
Stack content: 12,11,10,9,8,6,4,2,7,5,3,1