Skip to content

Commit

Permalink
Extended ProcessAid to all actor types.
Browse files Browse the repository at this point in the history
  • Loading branch information
devlaam committed Jul 13, 2023
1 parent 8317d74 commit 5e2b861
Show file tree
Hide file tree
Showing 4 changed files with 359 additions and 48 deletions.
Original file line number Diff line number Diff line change
@@ -1,7 +1,5 @@
package s2a.leucine.actors

import scala.quoted.ToExpr.NilToExpr

/**
* MIT License
*
Expand Down Expand Up @@ -30,65 +28,66 @@ import scala.quoted.ToExpr.NilToExpr
/* Methods stub for when there is no stash mixin used. */
private trait ProcessDefs extends BareDefs

/** Mixin if you need to store letters away. */
trait ProcessAid extends ActorInit, ActorDefs :
/** Mixin if you need to make use of partial functions for the message processing. */
private transparent trait ProcessAid extends ActorInit, ActorDefs :
this: BareActor =>

/* The stack holds all processes that were added by the user. Init is first (and default) process
* that is always available. It may be the only one. So calling `head` on the stack will never give
* rise to an exception. */
private val stack: StackQueue[Process] =
val init = new Process { def apply[Sender >: Common <: Accept] = process }
private val stack: StackQueue[RedirectBase] =
val init = new RedirectBase { def apply[Sender >: Common <: Accept] = processShared }
StackQueue(init)

/** Captures the viable unmatched handler and uses it in case a letter is not read. */
protected def unmatched[Sender >: Common <: Accept](letter: MyLetter[Sender], sender: Sender): Receive
private[actors] protected def unmatchedShared[Sender >: Common <: Accept](letter: MyLetter[Sender], sender: Sender): Receive

/* The receive method is overridden here and may not be implemented by the user any more. */
final protected def receive[Sender >: Common <: Accept](letter: MyLetter[Sender], sender: Sender): Receive =
/** Shared receive method, called by the receive method from the actor. */
private[actors] protected def receiveShared[Sender >: Common <: Accept](letter: MyLetter[Sender], sender: Sender): Receive =
/* Handles the situation where the message is not processed by the user.*/
def failing[Sender >: Common <: Accept]: PartialFunction[(MyLetter[Sender],Sender),Receive] =
case (letter,sender) => unmatched(letter,sender)
case (letter,sender) => unmatchedShared(letter,sender)
/* First try to process the message by the topmost process handler, if it fails, fallback on unmatched. */
(stack.head.apply orElse failing)(letter,sender)

/**
* This is the first process to be called and must be implemented by the user. The idea is that the user
* may implement more methods with comparable signature to be able to switch between them. */
protected def process[Sender >: Common <: Accept]: PartialFunction[(MyLetter[Sender],Sender),Receive]
/** Shared process method, to be indirectly called by the user. */
private[actors] protected def processShared[Sender >: Common <: Accept]: PartialFunction[(MyLetter[Sender],Sender),Receive]

/**
* Process class to be instantiated by the user to define a new process. See the Process object for
* examples how this can be done. */
protected trait Process :
private[actors] transparent protected trait RedirectBase :
/** Other processes can be implemented by defining apply in the instance of Process. */
def apply[Sender >: Common <: Accept]: PartialFunction[(MyLetter[Sender],Sender),Receive]

/** All user defined process actions are derived from this trait. */
protected sealed trait ProcessAction

/** Switch the process from one to an other, see the Process object for the correct use. */
def switch(action: Process.Action): Unit = action match
case proc: Process.Replace => stack.patch(proc)
case proc: Process.Push => stack.push(proc)
case Process.Pop(n) => stack.pop(n)
case Process.Keep(n) => stack.preserve(n)
case Process.Clear => stack.clear()
protected def switch(action: ProcessAction): Unit = action match
case p: ProcessBase#ReplaceBase => stack.patch(p)
case p: ProcessBase#PushBase => stack.push(p)
case p: ProcessBase#Pop => stack.pop(p.n)
case p: ProcessBase#Keep => stack.preserve(p.n)
case _: ProcessBase#Clear => stack.clear()

/** Object Process for user to manipulate the Process */
protected object Process :
/** All user defined process actions are derived from this trait. */
sealed trait Action
/** Base class for the access object to define the stack manipulation actions. */
private[actors] protected class ProcessBase :
/**
* Action to replace the current process by a new one. If this is the first 'new' process
* calling this is equivalent to a Process.Push, if not, the last process is remove.
* calling this is equivalent to a Process.Push, if not, the last process is removed.
* Define the new process as an apply method (precise signature depends on actor type):
* switch(new Process.Replace { def apply[Sender <: Accept] = myProcess1 } )
* switch(new Process.Replace { def process[Sender <: Accept] = myProcess1 } )
* where myProcess1 is the partial function containing an alternative treatment of all
* letters. */
abstract class Replace extends Process, Action
private[actors] abstract class ReplaceBase extends RedirectBase, ProcessAction
/**
* Action to add a new process on top of the current process. Define the new process
* as an apply method (precise signature depends on actor type):
* switch(new Process.Push { def apply[Sender <: Accept] = myProcess2 } )
* switch(new Process.Push { def process[Sender <: Accept] = myProcess2 } )
* where myProcess2 is the partial function containing an alternative treatment of all
* letters. */
abstract class Push extends Process, Action
private[actors] abstract class PushBase extends RedirectBase, ProcessAction
/**
* Removes the current process from the process stack, so the one before becomes the current
* one. If this action removes the last process, the initial process will be restored. You
Expand All @@ -97,22 +96,21 @@ trait ProcessAid extends ActorInit, ActorDefs :
* parameter, n is assumed to be 1. Use it like:
* switch(Process.Pop(2))
*/
case class Pop(n: Int = 1) extends Action
class Pop(val n: Int = 1) extends ProcessAction
/**
* Remove just enough process from the stack to keep n processes left. If there are already
* Remove just enough processes from the stack to keep n processes left. If there are already
* n or less processes present, no action is taken. if n <= 0, this is equivalent to a
* Process.Clear. Example of use:
* switch(Process.Keep(2)) */
case class Keep(n: Int) extends Action
class Keep(val n: Int) extends ProcessAction
/**
* Removes all processes from the stack and restores the initial process as the current one.
* Example of use:
* switch(Process.Clear) */
object Clear extends Action
* switch(Process.Clear()) */
class Clear extends ProcessAction

/* Called to count this trait */
private[actors] override def initCount: Int = super.initCount + 1

/* Signal that this trait is instantiated */
initReady()

Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
package s2a.leucine.actors

/**
* MIT License
*
* Copyright (c) 2023 Ruud Vlaming
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**/


/** Use this mixin with the RestrictActor to make use of partial functions in the message handling. */
trait RestrictProcess extends ProcessAid :
this: RestrictActor[?] =>

/** Alias for the partial function type expected for the restrict actor. */
protected type Process[Sender <: Accept] = PartialFunction[(Letter[Sender],Sender),Receive]

/* Redirected call to match types */
private[actors] protected def unmatchedShared[Sender >: Common <: Accept](letter: MyLetter[Sender], sender: Sender): Receive = unmatched(letter,sender)

/* Redirected call to match types */
private[actors] protected def processShared[Sender >: Common <: Accept]: PartialFunction[(MyLetter[Sender],Sender),Receive] = process

/** The receive method is overridden here and may not be implemented by the user any more. */
final protected def receive[Sender <: Accept](letter: Letter[Sender], sender: Sender): Receive = receiveShared(letter,sender)

/**
* This is the first process to be called and must be implemented by the user. The idea is that the user
* may implement more methods with comparable signature to be able to switch between them. */
protected def process[Sender <: Accept]: Process[Sender]

/** Redirection trait that is instantiated to store the new process call on the stack. */
transparent protected trait Redirect extends RedirectBase:
final def apply[Sender >: Common <: Accept] = process
/** Process method that should be defined by the user to handle the messages in an alternative manner. */
def process[Sender <: Accept]: Process[Sender]

/** Access object for the user to define the required actions. */
protected object Process extends ProcessBase:
/**
* Action to replace the current process by a new one. If this is the first 'new' process
* calling this is equivalent to a Process.Push, if not, the last process is removed.
* Define the new process as an apply method:
* switch(new Process.Replace { def process[Sender <: Accept] = myProcess1 } )
* where myProcess1 is the partial function containing an alternative treatment of all
* letters. */
abstract class Replace extends ReplaceBase, Redirect
/**
* Action to add a new process on top of the current process. Define the new process
* as an apply method:
* switch(new Process.Push { def process[Sender <: Accept] = myProcess2 } )
* where myProcess2 is the partial function containing an alternative treatment of all
* letters. */
abstract class Push extends PushBase, Redirect
Original file line number Diff line number Diff line change
@@ -0,0 +1,96 @@
package s2a.leucine.actors

/**
* MIT License
*
* Copyright (c) 2023 Ruud Vlaming
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
**/


/**
* Shared implementation for all actors where the Letter types do not depend on
* the Sender types. For internal use only. */
private transparent trait ProcessShareAid extends ProcessAid :
this: BareActor =>

/* Narrowed types for these set of actors. */
type Common = Accept
type Sender >: Common <: Accept
type Letter = MyLetter[Sender]

/** Alias for the partial function type expected for these set of actors. */
protected type Process = PartialFunction[(Letter,Sender),Receive]

/* Redirected call to match types */
private[actors] protected def unmatchedShared[Sender >: Common <: Accept](letter: MyLetter[Sender], sender: Sender): Receive = unmatched(letter,sender)

/* Redirected call to match types */
private[actors] protected def processShared[Sender >: Common <: Accept]: PartialFunction[(MyLetter[Sender],Sender),Receive] = process

/** The receive method is overridden here and may not be implemented by the user any more. */
final protected def receive(letter: Letter, sender: Sender): Receive = receiveShared(letter,sender)

/* Template reference for the unmatched handler */
protected def unmatched(letter: Letter, sender: Sender): Receive

/**
* This is the first process to be called and must be implemented by the user. The idea is that the user
* may implement more methods with comparable signature to be able to switch between them. */
protected def process: Process

/** Redirection trait that is instantiated to store the new process call on the stack. */
transparent protected trait Redirect extends RedirectBase:
final def apply[Sender >: Common <: Accept] = process
/** Process method that should be defined by the user to handle the messages in an alternative manner. */
def process: Process

/** Access object for the user to define the required actions. */
protected object Process extends ProcessBase:
/**
* Action to replace the current process by a new one. If this is the first 'new' process
* calling this is equivalent to a Process.Push, if not, the last process is removed.
* Define the new process as an apply method:
* switch(new Process.Replace { def process = myProcess1 } )
* where myProcess1 is the partial function containing an alternative treatment of all
* letters. */
abstract class Replace extends ReplaceBase, Redirect
/**
* Action to add a new process on top of the current process. Define the new process
* as an apply method:
* switch(new Process.Push { def process = myProcess2 } )
* where myProcess2 is the partial function containing an alternative treatment of all
* letters. */
abstract class Push extends PushBase, Redirect


/** Use this mixin with the WideActor to make use of partial functions in the message handling. */
trait WideProcess extends ProcessAid, ProcessShareAid :
this: WideActor[?] =>


/** Use this mixin with the AcceptActor to make use of partial functions in the message handling. */
trait AcceptProcess extends ProcessAid, ProcessShareAid :
this: AcceptActor[?] =>


/** Use this mixin with the SelectActor to make use of partial functions in the message handling. */
trait SelectProcess extends ProcessAid, ProcessShareAid :
this: SelectActor[?] =>
Loading

0 comments on commit 5e2b861

Please sign in to comment.