summaryrefslogtreecommitdiff
path: root/odcsim/odcsim-api/src/main/kotlin
diff options
context:
space:
mode:
authorFabian Mastenbroek <mail.fabianm@gmail.com>2019-11-29 16:05:09 +0100
committerFabian Mastenbroek <mail.fabianm@gmail.com>2019-11-29 16:05:09 +0100
commitce952cf3f27c154e06cfa56ca1ad7db9ba3eac7c (patch)
tree8203f7ba7ae829ed19e23d89950b6a1bf2bde2f8 /odcsim/odcsim-api/src/main/kotlin
parente6b6416e3d78178701ff5246141d3418967976fd (diff)
refactor: Rename odcsim-core to odcsim-api
This change renames the main module of the odcsim library to odcsim-api, since it mainly contains the interfaces to be used by consumers of the API and implemented by the various frameworks.
Diffstat (limited to 'odcsim/odcsim-api/src/main/kotlin')
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt175
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt142
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt50
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt76
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt38
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behavior.kt193
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt223
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Envelope.kt57
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Signals.kt60
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt88
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Time.kt75
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt92
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt116
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt66
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt42
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt43
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt48
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt201
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt182
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt74
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt122
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt567
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt440
-rw-r--r--odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt77
24 files changed, 3247 insertions, 0 deletions
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt
new file mode 100644
index 00000000..dc6ca7ec
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt
@@ -0,0 +1,175 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+import org.slf4j.Logger
+
+/**
+ * Represents the context in which the execution of an actor's behavior takes place.
+ *
+ * @param T The shape of the messages the actor accepts.
+ */
+interface ActorContext<T : Any> {
+ /**
+ * The identity of the actor, bound to the lifecycle of this actor instance.
+ */
+ val self: ActorRef<T>
+
+ /**
+ * A view of the children of this actor.
+ */
+ val children: List<ActorRef<*>>
+
+ /**
+ * The point of time within the simulation.
+ */
+ val time: Instant
+
+ /**
+ * The [ActorSystem] the actor is part of.
+ */
+ val system: ActorSystem<*>
+
+ /**
+ * An actor specific logger instance.
+ */
+ val log: Logger
+
+ /**
+ * Obtain the child of this actor with the specified name.
+ *
+ * @param name The name of the child actor to obtain.
+ * @return The reference to the child actor or `null` if it does not exist.
+ */
+ fun getChild(name: String): ActorRef<*>?
+
+ /**
+ * Send the specified message to the actor referenced by this [ActorRef].
+ *
+ * Please note that callees must guarantee that messages are sent strictly in increasing time.
+ * If so, this method guarantees that:
+ * - A message will never be received earlier than specified
+ * - A message might arrive later than specified if the two actors are not synchronized.
+ *
+ * @param ref The actor to send the message to.
+ * @param msg The message to send to the referenced actor.
+ * @param after The delay after which the message should be received by the actor.
+ */
+ fun <U : Any> send(ref: ActorRef<U>, msg: U, after: Duration = 0.0)
+
+ /**
+ * Spawn a child actor from the given [Behavior] and with the specified name.
+ *
+ * The name may not be empty or start with "$". Moreover, the name of an actor must be unique and this method
+ * will throw an [IllegalArgumentException] in case a child actor of the given name already exists.
+ *
+ * @param behavior The behavior of the child actor to spawn.
+ * @param name The name of the child actor to spawn.
+ * @return A reference to the child that has/will be spawned.
+ */
+ fun <U : Any> spawn(behavior: Behavior<U>, name: String): ActorRef<U>
+
+ /**
+ * Spawn an anonymous child actor from the given [Behavior].
+ *
+ * @param behavior The behavior of the child actor to spawn.
+ * @return A reference to the child that has/will be spawned.
+ */
+ fun <U : Any> spawnAnonymous(behavior: Behavior<U>): ActorRef<U>
+
+ /**
+ * Force the specified child actor to terminate after it finishes processing its current message.
+ * Nothing will happen if the child is already stopped.
+ *
+ * Only direct children of an actor may be stopped through the actor context. Trying to stop other actors via this
+ * method will result in an [IllegalArgumentException]. Instead, stopping other actors has to be expressed as
+ * an explicit stop message that the actor accept.
+ *
+ * @param child The reference to the child actor to stop.
+ */
+ fun stop(child: ActorRef<*>)
+
+ /**
+ * Watch the specified [ActorRef] for termination of the referenced actor. On termination of the watched actor,
+ * a [Terminated] signal is sent to this actor.
+ *
+ * @param target The target actor to watch.
+ */
+ fun watch(target: ActorRef<*>)
+
+ /**
+ * Revoke the registration established by [watch].
+ *
+ * In case there exists no registration for the specified [target], no action will be performed.
+ *
+ * @param target The target actor to unwatch.
+ */
+ fun unwatch(target: ActorRef<*>)
+
+ /**
+ * Synchronize the local virtual time of this target with the other referenced actor's local virtual time.
+ *
+ * By default, actors are not guaranteed to be synchronized, meaning that for some implementations, virtual time may
+ * drift between different actors. Synchronization between two actors ensures that virtual time remains consistent
+ * between at least the two actors.
+ *
+ * Be aware that this method may cause a jump in virtual time in order to get consistent with [target].
+ * Furthermore, please note that synchronization might incur performance degradation and should only be used
+ * when necessary.
+ *
+ * @param target The reference to the target actor to synchronize with.
+ */
+ fun sync(target: ActorRef<*>)
+
+ /**
+ * Desynchronize virtual time between two actors if possible.
+ *
+ * Please note that this method only provides a hint to the [ActorSystem] that it may drop synchronization between
+ * the actors, but [ActorSystem] is not compelled to actually do so (i.e. in the case where synchronization is
+ * always guaranteed).
+ *
+ * Furthermore, if [target] is already desychronized, the method should return without error. [ActorContext.isSync]
+ * may be used to determine if an actor is synchronized.
+ *
+ * @param target The reference to the target actor to desynchronize with.
+ */
+ fun unsync(target: ActorRef<*>)
+
+ /**
+ * Determine whether this actor and [target] are synchronized in virtual time.
+ *
+ * @param target The target to check for synchronization.
+ * @return `true` if [target] is synchronized with this actor, `false` otherwise.
+ */
+ fun isSync(target: ActorRef<*>): Boolean
+}
+
+/**
+ * Unsafe helper method for widening the type accepted by this [ActorContext].
+ */
+fun <U : Any, T : U> ActorContext<T>.unsafeCast(): ActorContext<U> {
+ @Suppress("UNCHECKED_CAST")
+ return this as ActorContext<U>
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt
new file mode 100644
index 00000000..a6c716a2
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt
@@ -0,0 +1,142 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+import java.io.Serializable
+
+/**
+ * An actor path represents the unique path to a specific actor instance within an [ActorSystem].
+ */
+sealed class ActorPath : Comparable<ActorPath>, Serializable {
+ /**
+ * The name of the actor that this path refers to.
+ */
+ abstract val name: String
+
+ /**
+ * The path for the parent actor.
+ */
+ abstract val parent: ActorPath
+
+ /**
+ * Walk up the tree to obtain and return the [ActorPath.Root].
+ */
+ abstract val root: Root
+
+ /**
+ * Create a new child actor path.
+ */
+ fun child(name: String): ActorPath = Child(this, name)
+
+ /**
+ * Create a new child actor path.
+ */
+ operator fun div(name: String): ActorPath = child(name)
+
+ /**
+ * Recursively create a descendant’s path by appending all child names.
+ */
+ fun descendant(children: Iterable<String>): ActorPath = children.fold(this) { parent, name ->
+ if (name.isNotBlank()) child(name) else parent
+ }
+
+ /**
+ * Root of the hierarchy of [ActorPath]s. There is exactly root per [ActorSystem].
+ */
+ data class Root(override val name: String = "/") : ActorPath() {
+ init {
+ require(name.length == 1 || name.indexOf('/', 1) == -1) {
+ "/ may only exist at the beginning of the root actors name"
+ }
+ require(name.indexOf('#') == -1) { "# may not exist in a path component" }
+ }
+
+ override val parent: ActorPath = this
+
+ override val root: Root = this
+
+ /**
+ * Compare the [specified][other] path with this root node for order. If both paths are roots, compare their
+ * name, otherwise the root is ordered higher.
+ *
+ * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater
+ * than the specified path.
+ */
+ override fun compareTo(other: ActorPath): Int = if (other is Root) name.compareTo(other.name) else 1
+
+ /**
+ * Create a string representation of this root node which prints its own [name].
+ *
+ * @return A string representation of this node.
+ */
+ override fun toString(): String = name
+ }
+
+ /**
+ * A child in the hierarchy of [ActorPath]s.
+ */
+ data class Child(override val parent: ActorPath, override val name: String) : ActorPath() {
+ init {
+ require(name.indexOf('/') == -1) { "/ may not exist in a path component" }
+ require(name.indexOf('#') == -1) { "# may not exist in a path component" }
+ }
+
+ override val root: Root by lazy {
+ when (parent) {
+ is Root -> parent
+ else -> parent.root
+ }
+ }
+
+ /**
+ * Compare the [specified][other] path with this child node for order.
+ *
+ * @return a negative integer, zero, or a positive integer as this object is less than, equal to, or greater
+ * than the specified path.
+ */
+ override fun compareTo(other: ActorPath): Int {
+ tailrec fun rec(left: ActorPath, right: ActorPath): Int = when {
+ left == right -> 0
+ left is Root -> left.compareTo(right)
+ right is Root -> -(right.compareTo(left))
+ else -> {
+ val x = left.name.compareTo(right.name)
+ if (x == 0)
+ rec(left.parent, right.parent)
+ else
+ x
+ }
+ }
+ return rec(this, other)
+ }
+
+ /**
+ * Create a string representation of this child node which prints the name of [parent] and its own [name].
+ *
+ * @return A string representation of this node.
+ */
+ override fun toString(): String = "$parent/$name"
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt
new file mode 100644
index 00000000..45fc756e
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt
@@ -0,0 +1,50 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+import java.io.Serializable
+
+/**
+ * A reference to an entity in simulation that accepts messages of type [T].
+ */
+interface ActorRef<in T : Any> : Comparable<ActorRef<*>>, Serializable {
+ /**
+ * The path for this actor (from this actor up to the root actor).
+ */
+ val path: ActorPath
+
+ /**
+ * Compare this reference to another actor reference.
+ */
+ override fun compareTo(other: ActorRef<*>): Int = path.compareTo(other.path)
+}
+
+/**
+ * Unsafe helper method for widening the type accepted by this [ActorRef].
+ */
+fun <U : Any, T : U> ActorRef<T>.unsafeCast(): ActorRef<U> {
+ @Suppress("UNCHECKED_CAST")
+ return this as ActorRef<U>
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt
new file mode 100644
index 00000000..d65beebd
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt
@@ -0,0 +1,76 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+/**
+ * An actor system is a hierarchical grouping of actors that represents a discrete event simulation.
+ *
+ * An implementation of this interface should be provided by an engine. See for example *odcsim-engine-omega*,
+ * which is the reference implementation of the *odcsim* API.
+ *
+ * @param T The shape of the messages the root actor in the system can receive.
+ */
+interface ActorSystem<in T : Any> : ActorRef<T> {
+ /**
+ * The current point in simulation time.
+ */
+ val time: Instant
+
+ /**
+ * The name of this engine instance, used to distinguish between multiple engines running within the same JVM.
+ */
+ val name: String
+
+ /**
+ * Run the actors until the specified point in simulation time.
+ *
+ * @param until The point until which the simulation should run.
+ */
+ fun run(until: Duration = Duration.POSITIVE_INFINITY)
+
+ /**
+ * Send the specified message to the root actor of this [ActorSystem].
+ *
+ * @param msg The message to send to the referenced actor.
+ * @param after The delay after which the message should be received by the actor.
+ */
+ fun send(msg: T, after: Duration = 0.1)
+
+ /**
+ * Terminates this actor system in an asynchronous fashion.
+ *
+ * This will stop the root actor and in turn will recursively stop all its child actors.
+ */
+ fun terminate()
+
+ /**
+ * Create an actor in the "/system" namespace. This actor will be shut down during `system.terminate()` only after
+ * all user actors have terminated.
+ *
+ * @param behavior The behavior of the system actor to spawn.
+ * @param name The name of the system actor to spawn.
+ */
+ suspend fun <U : Any> spawnSystem(behavior: Behavior<U>, name: String): ActorRef<U>
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt
new file mode 100644
index 00000000..f59bc966
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt
@@ -0,0 +1,38 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+/**
+ * A factory for [ActorSystem] instances that allows users to dynamically load engine implementations.
+ */
+interface ActorSystemFactory {
+ /**
+ * Create an [ActorSystem] with the given root [Behavior] and the given name.
+ *
+ * @param root The behavior of the root actor.
+ * @param name The name of the engine instance.
+ */
+ operator fun <T : Any> invoke(root: Behavior<T>, name: String): ActorSystem<T>
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behavior.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behavior.kt
new file mode 100644
index 00000000..9ad7f83f
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behavior.kt
@@ -0,0 +1,193 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+/**
+ * The representation of the behavior of an actor.
+ *
+ * Behavior can be formulated using factory methods on the companion object or by extending either [DeferredBehavior] or
+ * [ReceivingBehavior].
+ *
+ * Users are advised not to close over [ActorContext] within [Behavior], as it will causes it to become immobile,
+ * meaning it cannot be moved to another context and executed there, and therefore it cannot be replicated or forked
+ * either.
+ *
+ * @param T The shape of the messages the behavior accepts.
+ */
+sealed class Behavior<T : Any> {
+ /**
+ * Narrow the type of this behavior.
+ *
+ * This is almost always a safe operation, but might cause [ClassCastException] in case a narrowed behavior sends
+ * messages of a different type to itself and is chained via [Behavior.orElse].
+ */
+ fun <U : T> narrow(): Behavior<U> = unsafeCast()
+
+ /**
+ * Widen the type of this behavior by placing a funnel in front of it.
+ *
+ * @param transform The mapping from the widened type to the original type, returning `null` in-case the message
+ * should not be handled.
+ */
+ fun <U : Any> widen(transform: (U) -> T?): Behavior<U> {
+ return wrap(this) { interpreter ->
+ receive<U> { ctx, msg ->
+ val res = transform(msg)
+ @Suppress("UNCHECKED_CAST")
+ if (res == null || interpreter.interpretMessage(ctx as ActorContext<T>, res))
+ unhandled()
+ else
+ interpreter.behavior.unsafeCast()
+ }.unsafeCast()
+ }.unsafeCast()
+ }
+
+ /**
+ * Compose this [Behavior] with a fallback [Behavior] which is used in case this [Behavior] does not handle the
+ * incoming message or signal.
+ *
+ * @param that The fallback behavior.
+ */
+ fun orElse(that: Behavior<T>): Behavior<T> =
+ wrap(this) { left ->
+ wrap(that) { right ->
+ object : ReceivingBehavior<T>() {
+ override fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> {
+ if (left.interpretMessage(ctx, msg)) {
+ return left.behavior
+ } else if (right.interpretMessage(ctx, msg)) {
+ return right.behavior
+ }
+
+ return unhandled()
+ }
+
+ override fun receiveSignal(ctx: ActorContext<T>, signal: Signal): Behavior<T> {
+ if (left.interpretSignal(ctx, signal)) {
+ return left.behavior
+ } else if (right.interpretSignal(ctx, signal)) {
+ return right.behavior
+ }
+
+ return unhandled()
+ }
+ }
+ }
+ }
+
+ /**
+ * Unsafe utility method for changing the type accepted by this [Behavior].
+ * Be aware that changing the type might result in [ClassCastException], when sending a message to the resulting
+ * behavior.
+ */
+ fun <U : Any> unsafeCast(): Behavior<U> {
+ @Suppress("UNCHECKED_CAST")
+ return this as Behavior<U>
+ }
+}
+
+/**
+ * A [Behavior] that defers the construction of the actual [Behavior] until the actor is started in some [ActorContext].
+ * If the actor is already started, it will immediately evaluate.
+ *
+ * @param T The shape of the messages the behavior accepts.
+ */
+abstract class DeferredBehavior<T : Any> : Behavior<T>() {
+ /**
+ * Create a [Behavior] instance in the [specified][ctx] [ActorContext].
+ *
+ * @param ctx The [ActorContext] in which the behavior runs.
+ * @return The actor's next behavior.
+ */
+ abstract operator fun invoke(ctx: ActorContext<T>): Behavior<T>
+}
+
+/**
+ * A [Behavior] that concretely defines how an actor will react to the messages and signals it receives.
+ * The message may either be of the type that the actor declares and which is part of the [ActorRef] signature,
+ * or it may be a system [Signal] that expresses a lifecycle event of either this actor or one of its child actors.
+ *
+ * @param T The shape of the messages the behavior accepts.
+ */
+abstract class ReceivingBehavior<T : Any> : Behavior<T>() {
+ /**
+ * Process an incoming message of type [T] and return the actor's next behavior.
+ *
+ * The returned behavior can in addition to normal behaviors be one of the canned special objects:
+ * - returning [stopped] will terminate this Behavior
+ * - returning [same] designates to reuse the current Behavior
+ * - returning [unhandled] keeps the same Behavior and signals that the message was not yet handled
+ *
+ * @param ctx The [ActorContext] in which the actor is currently running.
+ * @param msg The message that was received.
+ */
+ open fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> = unhandled()
+
+ /**
+ * Process an incoming [Signal] and return the actor's next behavior.
+ *
+ * The returned behavior can in addition to normal behaviors be one of the canned special objects:
+ * - returning [stopped] will terminate this Behavior
+ * - returning [same] designates to reuse the current Behavior
+ * - returning [unhandled] keeps the same Behavior and signals that the message was not yet handled
+ *
+ * @param ctx The [ActorContext] in which the actor is currently running.
+ * @param signal The [Signal] that was received.
+ */
+ open fun receiveSignal(ctx: ActorContext<T>, signal: Signal): Behavior<T> = unhandled()
+}
+
+/**
+ * A flag to indicate whether a [Behavior] instance is still alive.
+ */
+val <T : Any> Behavior<T>.isAlive get() = this !is StoppedBehavior
+
+/**
+ * A flag to indicate whether the last message/signal went unhandled.
+ */
+val <T : Any> Behavior<T>.isUnhandled get() = this is UnhandledBehavior
+
+// The special behaviors are kept in this file as to be able to seal the Behavior class to prevent users from extending
+// it.
+/**
+ * A special [Behavior] instance that signals that the actor has stopped.
+ */
+internal object StoppedBehavior : Behavior<Any>() {
+ override fun toString() = "Stopped"
+}
+
+/**
+ * A special [Behavior] object to signal that the actor wants to reuse its previous behavior.
+ */
+internal object SameBehavior : Behavior<Nothing>() {
+ override fun toString() = "Same"
+}
+
+/**
+ * A special [Behavior] object that indicates that the last message or signal was not handled.
+ */
+internal object UnhandledBehavior : Behavior<Nothing>() {
+ override fun toString() = "Unhandled"
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt
new file mode 100644
index 00000000..eac254ec
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt
@@ -0,0 +1,223 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+@file:JvmName("Behaviors")
+package com.atlarge.odcsim
+
+import com.atlarge.odcsim.internal.BehaviorInterpreter
+import com.atlarge.odcsim.internal.EmptyBehavior
+import com.atlarge.odcsim.internal.IgnoreBehavior
+import com.atlarge.odcsim.internal.TimerSchedulerImpl
+import com.atlarge.odcsim.internal.sendSignal
+
+/**
+ * This [Behavior] is used to signal that this actor shall terminate voluntarily. If this actor has created child actors
+ * then these will be stopped as part of the shutdown procedure.
+ */
+fun <T : Any> stopped(): Behavior<T> = StoppedBehavior.unsafeCast()
+
+/**
+ * This [Behavior] is used to signal that this actor wants to reuse its previous behavior.
+ */
+fun <T : Any> same(): Behavior<T> = SameBehavior.unsafeCast()
+
+/**
+ * This [Behavior] is used to signal to the system that the last message or signal went unhandled. This will
+ * reuse the previous behavior.
+ */
+fun <T : Any> unhandled(): Behavior<T> = UnhandledBehavior.unsafeCast()
+
+/**
+ * A factory for [Behavior]. Creation of the behavior instance is deferred until the actor is started.
+ */
+fun <T : Any> setup(block: (ActorContext<T>) -> Behavior<T>): Behavior<T> {
+ return object : DeferredBehavior<T>() {
+ override fun invoke(ctx: ActorContext<T>): Behavior<T> = block(ctx)
+ }
+}
+
+/**
+ * A [Behavior] that ignores any incoming message or signal and keeps the same behavior.
+ */
+fun <T : Any> ignore(): Behavior<T> = IgnoreBehavior.narrow()
+
+/**
+ * A [Behavior] that treats every incoming message or signal as unhandled.
+ */
+fun <T : Any> empty(): Behavior<T> = EmptyBehavior.narrow()
+
+/**
+ * Construct a [Behavior] that reacts to incoming messages, provides access to the [ActorContext] and returns the
+ * actor's next behavior.
+ */
+fun <T : Any> receive(handler: (ActorContext<T>, T) -> Behavior<T>): Behavior<T> {
+ return object : ReceivingBehavior<T>() {
+ override fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> = handler(ctx, msg)
+ }
+}
+
+/**
+ * Construct a [Behavior] that reacts to incoming messages of type [U], provides access to the [ActorContext] and
+ * returns the actor's next behavior. Other messages will be unhandled.
+ */
+inline fun <T : Any, reified U : T> receiveOf(crossinline handler: (ActorContext<T>, U) -> Behavior<T>): Behavior<T> {
+ return object : ReceivingBehavior<T>() {
+ override fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> {
+ return if (msg is U)
+ handler(ctx, msg)
+ else
+ unhandled()
+ }
+ }
+}
+
+/**
+ * Construct a [Behavior] that reacts to incoming messages and returns the actor's next behavior.
+ */
+fun <T : Any> receiveMessage(handler: (T) -> Behavior<T>): Behavior<T> {
+ return object : ReceivingBehavior<T>() {
+ override fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> = handler(msg)
+ }
+}
+
+/**
+ * Construct a [Behavior] that reacts to incoming signals, provides access to the [ActorContext] and returns the
+ * actor's next behavior.
+ */
+fun <T : Any> receiveSignal(handler: (ActorContext<T>, Signal) -> Behavior<T>): Behavior<T> {
+ return object : ReceivingBehavior<T>() {
+ override fun receiveSignal(ctx: ActorContext<T>, signal: Signal): Behavior<T> = handler(ctx, signal)
+ }
+}
+
+/**
+ * Construct a [Behavior] that wraps another behavior instance and uses a [BehaviorInterpreter] to pass incoming
+ * messages and signals to the wrapper behavior.
+ */
+fun <T : Any> wrap(behavior: Behavior<T>, wrap: (BehaviorInterpreter<T>) -> Behavior<T>): Behavior<T> {
+ return setup { ctx -> wrap(BehaviorInterpreter(behavior, ctx)) }
+}
+
+/**
+ * Obtain a [TimerScheduler] for building a [Behavior] instance.
+ */
+fun <T : Any> withTimers(handler: (TimerScheduler<T>) -> Behavior<T>): Behavior<T> {
+ return setup { ctx ->
+ val scheduler = TimerSchedulerImpl(ctx)
+ receiveSignal<T> { _, signal ->
+ if (signal is TimerSchedulerImpl.TimerSignal) {
+ val res = scheduler.interceptTimerSignal(signal)
+ if (res != null) {
+ ctx.send(ctx.self, res)
+ return@receiveSignal same()
+ }
+ }
+ unhandled()
+ }.join(handler(scheduler))
+ }
+}
+
+/**
+ * Construct a [Behavior] that waits for the specified duration before constructing the next behavior.
+ *
+ * @param after The delay before constructing the next behavior.
+ * @param handler The handler to construct the behavior with.
+ */
+fun <T : Any> withTimeout(after: Duration, handler: (ActorContext<T>) -> Behavior<T>): Behavior<T> =
+ setup { ctx ->
+ val target = Any()
+ ctx.sendSignal(ctx.self, Timeout(target), after)
+ receiveSignal { _, signal ->
+ if (signal is Timeout && signal.target == target) {
+ handler(ctx)
+ } else {
+ unhandled()
+ }
+ }
+ }
+
+/**
+ * Join together both [Behavior] with another [Behavior], essentially running them side-by-side, only directly
+ * propagating stopped behavior.
+ *
+ * @param that The behavior to join with.
+ */
+fun <T : Any> Behavior<T>.join(that: Behavior<T>): Behavior<T> =
+ wrap(this) { left ->
+ wrap(that) { right ->
+ object : ReceivingBehavior<T>() {
+ override fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> {
+ if (left.interpretMessage(ctx, msg)) {
+ return left.propagate(this) // Propagate stopped behavior
+ } else if (right.interpretMessage(ctx, msg)) {
+ return right.propagate(this)
+ }
+
+ return unhandled()
+ }
+
+ override fun receiveSignal(ctx: ActorContext<T>, signal: Signal): Behavior<T> {
+ if (left.interpretSignal(ctx, signal)) {
+ return left.propagate(this)
+ } else if (right.interpretSignal(ctx, signal)) {
+ return right.propagate(this)
+ }
+
+ return unhandled()
+ }
+ }
+ }
+ }
+
+/**
+ * Widen the type of messages the [Behavior] by marking all other messages as unhandled.
+ */
+inline fun <U : Any, reified T : U> Behavior<T>.widen(): Behavior<U> = widen {
+ if (it is T)
+ it
+ else
+ null
+}
+
+/**
+ * Keep the specified [Behavior] alive if it returns the stopped behavior.
+ */
+fun <T : Any> Behavior<T>.keepAlive(): Behavior<T> =
+ wrap(this) { interpreter ->
+ object : ReceivingBehavior<T>() {
+ override fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> {
+ if (interpreter.interpretMessage(ctx, msg)) {
+ return this
+ }
+ return empty()
+ }
+
+ override fun receiveSignal(ctx: ActorContext<T>, signal: Signal): Behavior<T> {
+ if (interpreter.interpretSignal(ctx, signal)) {
+ return this
+ }
+
+ return empty()
+ }
+ }
+ }
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Envelope.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Envelope.kt
new file mode 100644
index 00000000..3b73d52d
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Envelope.kt
@@ -0,0 +1,57 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+import java.io.Serializable
+
+/**
+ * A timestamped wrapper for messages that will be delivered to an actor.
+ */
+interface Envelope<T : Any> : Comparable<Envelope<*>>, Serializable {
+ /**
+ * The time at which this message should be delivered.
+ */
+ val time: Instant
+
+ /**
+ * The message contained in this envelope, of type [T]
+ */
+ val message: T
+
+ /**
+ * Extract the delivery time from the envelope.
+ */
+ operator fun component1(): Instant = time
+
+ /**
+ * Extract the message from this envelope.
+ */
+ operator fun component2(): T = message
+
+ /**
+ * Compare this envelope to the [other] envelope, ordered increasingly in time.
+ */
+ override fun compareTo(other: Envelope<*>): Int = time.compareTo(other.time)
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Signals.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Signals.kt
new file mode 100644
index 00000000..9b707348
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Signals.kt
@@ -0,0 +1,60 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+/**
+ * System signals are notifications that are generated by the system and delivered to the actor behavior in a reliable
+ * fashion.
+ */
+interface Signal
+
+/**
+ * Lifecycle signal that is fired upon creation of the actor. This will be the first message that the actor receives.
+ */
+object PreStart : Signal
+
+/**
+ * Lifecycle signal that is fired after this actor and all its child actors (transitively) have terminated.
+ * The [Terminated] signal is only sent to registered watchers after this signal has been processed.
+ */
+object PostStop : Signal
+
+/**
+ * A lifecycle signal to indicate that an actor that was watched has terminated.
+ *
+ * @property ref The reference to the actor that has terminated.
+ * @property failure The failure that caused the termination, or `null` on graceful termination.
+ */
+data class Terminated(val ref: ActorRef<*>, val failure: Throwable? = null) : Signal
+
+/**
+ * A [Signal] to indicate an actor has timed out.
+ *
+ * This class contains a [target] property in order to allow nested behavior to function properly when multiple layers
+ * are waiting on this signal.
+ *
+ * @property target The target object that has timed out.
+ */
+data class Timeout(val target: Any) : Signal
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt
new file mode 100644
index 00000000..5d73d808
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt
@@ -0,0 +1,88 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+import com.atlarge.odcsim.internal.StashBufferImpl
+
+/**
+ * A non thread safe mutable message buffer that can be used to buffer messages inside actors and then unstash them.
+ *
+ * @param T The shape of the messages in this buffer.
+ */
+interface StashBuffer<T : Any> {
+ /**
+ * The first element of the buffer.
+ *
+ * @throws NoSuchElementException if the buffer is empty.
+ */
+ val head: T
+
+ /**
+ * A flag to indicate whether the buffer is empty.
+ */
+ val isEmpty: Boolean
+
+ /**
+ * A flag to indicate whether the buffer is full.
+ */
+ val isFull: Boolean
+
+ /**
+ * The number of elements in the stash buffer.
+ */
+ val size: Int
+
+ /**
+ * Iterate over all elements of the buffer and apply a function to each element, without removing them.
+ *
+ * @param block The function to invoke for each element.
+ */
+ fun forEach(block: (T) -> Unit)
+
+ /**
+ * Add one element to the end of the message buffer.
+ *
+ * @param msg The message to stash.
+ * @throws IllegalStateException if the element cannot be added at this time due to capacity restrictions
+ */
+ fun stash(msg: T)
+
+ /**
+ * Process all stashed messages with the behavior and the returned [Behavior] from each processed message.
+ *
+ * @param ctx The actor context to process these messages in.
+ * @param behavior The behavior to process the messages with.
+ */
+ fun unstashAll(ctx: ActorContext<T>, behavior: Behavior<T>): Behavior<T>
+
+ companion object {
+ /**
+ * Construct a [StashBuffer] with the specified [capacity].
+ *
+ * @param capacity The capacity of the buffer.
+ */
+ operator fun <T : Any> invoke(capacity: Int): StashBuffer<T> = StashBufferImpl(capacity)
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Time.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Time.kt
new file mode 100644
index 00000000..f19f6fe2
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Time.kt
@@ -0,0 +1,75 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+/**
+ * An instantaneous point on the time-line, used to record message time-stamps in a simulation.
+ */
+typealias Instant = Double
+
+/**
+ * A time interval which represents the amount of elapsed time between two messages.
+ */
+typealias Duration = Double
+
+/**
+ * Convert this [Int] into an [Instant].
+ */
+fun Int.toInstant(): Instant = toDouble()
+
+/**
+ * Convert this [Int] into a [Duration].
+ */
+fun Int.toDuration(): Duration = toDouble()
+
+/**
+ * Convert this [Long] into an [Instant].
+ */
+fun Long.toInstant(): Instant = toDouble()
+
+/**
+ * Convert this [Long] into a [Duration].
+ */
+fun Long.toDuration(): Duration = toDouble()
+
+/**
+ * Convert this [Float] into an [Instant].
+ */
+fun Float.toInstant(): Instant = toDouble()
+
+/**
+ * Convert this [Float] into a [Duration].
+ */
+fun Float.toDuration(): Duration = toDouble()
+
+/**
+ * Convert this [Double] into an [Instant].
+ */
+fun Double.toInstant(): Instant = toDouble()
+
+/**
+ * Convert this [Double] into a [Duration].
+ */
+fun Double.toDuration(): Duration = toDouble()
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt
new file mode 100644
index 00000000..c5c54b64
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt
@@ -0,0 +1,92 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim
+
+/**
+ * An interface to provide support for scheduled self messages in an actor. It is used with [withTimers].
+ * Timers are bound to the lifecycle of the actor that owns it, and thus are cancelled automatically when it is
+ * restarted or stopped.
+ *
+ * Please be aware that [TimerScheduler] is not thread-safe and must only be used within the actor that owns it.
+ *
+ * @param T The shape of the messages the owning actor of this scheduling accepts.
+ */
+interface TimerScheduler<T : Any> {
+ /**
+ * Cancel a timer with the given key.
+ *
+ * @param key The key of the timer.
+ */
+ fun cancel(key: Any)
+
+ /**
+ * Cancel all timers.
+ */
+ fun cancelAll()
+
+ /**
+ * Check if a timer with a given [key] is active.
+ *
+ * @param key The key to check if it is active.
+ * @return `true` if a timer with the specified key is active, `false` otherwise.
+ */
+ fun isTimerActive(key: Any): Boolean
+
+ /**
+ * Start a periodic timer that will send [msg] to the `self` actor at a fixed [interval].
+ *
+ * @param key The key of the timer.
+ * @param msg The message to send to the actor.
+ * @param interval The interval of simulation time after which it should be sent.
+ */
+ fun startPeriodicTimer(key: Any, msg: T, interval: Duration)
+
+ /**
+ * Start a timer that will send [msg] once to the `self` actor after the given [delay].
+ *
+ * @param key The key of the timer.
+ * @param msg The message to send to the actor.
+ * @param delay The delay in simulation time after which it should be sent.
+ */
+ fun startSingleTimer(key: Any, msg: T, delay: Duration)
+
+ /**
+ * Run [block] periodically at a fixed [interval]
+ *
+ * @param key The key of the timer.
+ * @param interval The delay of simulation time after which the block should run.
+ * @param block The block to run.
+ */
+ fun every(key: Any, interval: Duration, block: () -> Unit)
+
+ /**
+ * Run [block] after the specified [delay].
+ *
+ * @param key The key of the timer.
+ * @param delay The delay in simulation time after which the block should run.
+ * @param block The block to run.
+ */
+ fun after(key: Any, delay: Duration, block: () -> Unit)
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt
new file mode 100644
index 00000000..eb26add1
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt
@@ -0,0 +1,116 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.coroutines
+
+import com.atlarge.odcsim.ActorContext
+import com.atlarge.odcsim.Behavior
+import com.atlarge.odcsim.DeferredBehavior
+import com.atlarge.odcsim.Signal
+import com.atlarge.odcsim.internal.SuspendingActorContextImpl
+import com.atlarge.odcsim.internal.SuspendingBehaviorImpl
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.intrinsics.suspendCoroutineUninterceptedOrReturn
+import kotlin.coroutines.suspendCoroutine
+
+/**
+ * A [Behavior] that allows method calls to suspend execution via Kotlin coroutines.
+ *
+ * @param T The shape of the messages the actor accepts.
+ */
+abstract class SuspendingBehavior<T : Any> : DeferredBehavior<T>() {
+ /**
+ * Run the suspending logic of this behavior.
+ *
+ * @param ctx The [SuspendingActorContext] in which the behavior is executed.
+ * @return The next behavior for the actor.
+ */
+ abstract suspend operator fun invoke(ctx: SuspendingActorContext<T>): Behavior<T>
+
+ // Immediately transfer to implementation
+ override fun invoke(ctx: ActorContext<T>): Behavior<T> = SuspendingBehaviorImpl(ctx, this).start()
+}
+
+/**
+ * An [ActorContext] that provides additional functionality for receiving messages and signals from
+ * the actor's mailbox.
+ *
+ * @param T The shape of the messages the actor accepts.
+ */
+interface SuspendingActorContext<T : Any> : ActorContext<T>, CoroutineContext.Element {
+ /**
+ * Suspend execution of the active coroutine to wait for a message of type [T] to be received in the actor's
+ * mailbox. During suspension, incoming signals will be marked unhandled.
+ *
+ * @return The message of type [T] that has been received.
+ */
+ suspend fun receive(): T
+
+ /**
+ * Suspend execution of the active coroutine to wait for a [Signal] to be received in the actor's mailbox.
+ * During suspension, incoming messages will be marked unhandled.
+ *
+ * @return The [Signal] that has been received.
+ */
+ suspend fun receiveSignal(): Signal
+
+ /**
+ * A key to provide access to the untyped [SuspendingActorContext] via [CoroutineContext] for suspending methods
+ * running inside a [SuspendingBehavior].
+ */
+ companion object Key : CoroutineContext.Key<SuspendingActorContext<*>>
+}
+
+/**
+ * Obtains the current continuation instance inside suspend functions and suspends currently running coroutine. [block]
+ * should return a [Behavior] that will resume the continuation and return the next behavior which is supplied via the
+ * second argument of the block.
+ */
+suspend fun <T : Any, U> suspendWithBehavior(block: (Continuation<U>, () -> Behavior<T>) -> Behavior<T>): U =
+ suspendCoroutine { cont ->
+ @Suppress("UNCHECKED_CAST")
+ val ctx = cont.context[SuspendingActorContext] as? SuspendingActorContextImpl<T>
+ ?: throw UnsupportedOperationException("Coroutine does not run inside SuspendingBehavior")
+ ctx.become(block(cont) { ctx.behavior })
+ }
+
+/**
+ * Obtain the current [SuspendingActorContext] instance for the active continuation.
+ */
+suspend fun <T : Any> actorContext(): SuspendingActorContext<T> =
+ suspendCoroutineUninterceptedOrReturn { cont ->
+ @Suppress("UNCHECKED_CAST")
+ cont.context[SuspendingActorContext] as? SuspendingActorContext<T>
+ ?: throw UnsupportedOperationException("Coroutine does not run inside SuspendingBehavior")
+ }
+
+/**
+ * Construct a [Behavior] that uses Kotlin coroutines functionality to handle incoming messages and signals.
+ */
+fun <T : Any> suspending(block: suspend (SuspendingActorContext<T>) -> Behavior<T>): Behavior<T> {
+ return object : SuspendingBehavior<T>() {
+ override suspend fun invoke(ctx: SuspendingActorContext<T>) = block(ctx)
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt
new file mode 100644
index 00000000..e995c0e3
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt
@@ -0,0 +1,66 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.coroutines.dsl
+
+import com.atlarge.odcsim.ActorRef
+import com.atlarge.odcsim.Duration
+import com.atlarge.odcsim.coroutines.SuspendingActorContext
+import com.atlarge.odcsim.coroutines.suspendWithBehavior
+import com.atlarge.odcsim.receiveMessage
+import com.atlarge.odcsim.unhandled
+import kotlin.coroutines.resume
+
+/**
+ * Receive only messages of type [U] and mark all other messages as unhandled.
+ *
+ * @return The received message.
+ */
+suspend inline fun <T : Any, reified U : T> SuspendingActorContext<T>.receiveOf(): U =
+ suspendWithBehavior<T, U> { cont, next ->
+ receiveMessage { msg ->
+ if (msg is U) {
+ cont.resume(msg)
+ next()
+ } else {
+ unhandled()
+ }
+ }
+ }
+
+/**
+ * Send the specified message to the given reference and wait for a reply.
+ *
+ * @param ref The actor to send the message to.
+ * @param after The delay after which the message should be received by the actor.
+ * @param transform The block to transform `self` to a message.
+ */
+suspend inline fun <T : Any, U : Any, reified V : T> SuspendingActorContext<T>.ask(
+ ref: ActorRef<U>,
+ after: Duration = 0.0,
+ transform: (ActorRef<T>) -> U
+): V {
+ send(ref, transform(self), after)
+ return receiveOf()
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt
new file mode 100644
index 00000000..16b6f534
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt
@@ -0,0 +1,42 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.coroutines.dsl
+
+import com.atlarge.odcsim.Duration
+import com.atlarge.odcsim.coroutines.suspendWithBehavior
+import com.atlarge.odcsim.withTimeout
+import kotlin.coroutines.resume
+
+/**
+ * Block execution for the specified duration.
+ *
+ * @param after The duration after which execution should continue.
+ */
+suspend fun timeout(after: Duration) = suspendWithBehavior<Any, Unit> { cont, next ->
+ withTimeout(after) {
+ cont.resume(Unit)
+ next()
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt
new file mode 100644
index 00000000..f1aba25e
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt
@@ -0,0 +1,43 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal
+
+import com.atlarge.odcsim.ActorContext
+import com.atlarge.odcsim.ActorRef
+import com.atlarge.odcsim.Duration
+import com.atlarge.odcsim.Signal
+
+/**
+ * Send the specified [Signal] to the given actor reference after the specified duration.
+ *
+ * @param ref The actor to send the signal to.
+ * @param signal The signal to send to the referenced actor.
+ * @param after The delay after which the signal should be received by the actor.
+ */
+fun ActorContext<*>.sendSignal(ref: ActorRef<*>, signal: Signal, after: Duration = 0.0) {
+ // Signals are currently processed as regular messages
+ @Suppress("UNCHECKED_CAST")
+ send(ref as ActorRef<Any>, signal, after)
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt
new file mode 100644
index 00000000..b07cabc0
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt
@@ -0,0 +1,48 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal
+
+import com.atlarge.odcsim.ActorContext
+import com.atlarge.odcsim.Behavior
+import com.atlarge.odcsim.ReceivingBehavior
+import com.atlarge.odcsim.Signal
+
+/**
+ * A [Behavior] object that ignores all messages sent to the actor.
+ */
+internal object IgnoreBehavior : ReceivingBehavior<Any>() {
+ override fun receive(ctx: ActorContext<Any>, msg: Any): Behavior<Any> = this
+
+ override fun receiveSignal(ctx: ActorContext<Any>, signal: Signal): Behavior<Any> = this
+
+ override fun toString() = "Ignore"
+}
+
+/**
+ * A [Behavior] object that does not handle any message it receives.
+ */
+internal object EmptyBehavior : ReceivingBehavior<Any>() {
+ override fun toString() = "Empty"
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt
new file mode 100644
index 00000000..194c2a62
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt
@@ -0,0 +1,201 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal
+
+import com.atlarge.odcsim.ActorContext
+import com.atlarge.odcsim.Behavior
+import com.atlarge.odcsim.DeferredBehavior
+import com.atlarge.odcsim.ReceivingBehavior
+import com.atlarge.odcsim.SameBehavior
+import com.atlarge.odcsim.Signal
+import com.atlarge.odcsim.StoppedBehavior
+import com.atlarge.odcsim.UnhandledBehavior
+import com.atlarge.odcsim.isAlive
+import com.atlarge.odcsim.isUnhandled
+
+/**
+ * Helper class that interprets messages/signals, canonicalizes special objects and manages the life-cycle of
+ * [Behavior] instances.
+ *
+ * @param initialBehavior The initial behavior to use.
+ */
+class BehaviorInterpreter<T : Any>(initialBehavior: Behavior<T>) {
+ /**
+ * The current [Behavior] instance.
+ */
+ var behavior: Behavior<T> = initialBehavior
+ private set
+
+ /**
+ * A flag to indicate the interpreter is still alive.
+ */
+ val isAlive: Boolean get() = behavior.isAlive
+
+ /**
+ * Construct a [BehaviorInterpreter] with the specified initial behavior and immediately start it in the specified
+ * context.
+ *
+ * @param initialBehavior The initial behavior of the actor.
+ * @param ctx The [ActorContext] to run the behavior in.
+ */
+ constructor(initialBehavior: Behavior<T>, ctx: ActorContext<T>) : this(initialBehavior) {
+ start(ctx)
+ }
+
+ /**
+ * Start the initial behavior.
+ *
+ * @param ctx The [ActorContext] to start the behavior in.
+ */
+ fun start(ctx: ActorContext<T>) {
+ behavior = validateAsInitial(start(ctx, behavior))
+ }
+
+ /**
+ * Stop the current active behavior and move into the stopped state.
+ *
+ * @param ctx The [ActorContext] this takes place in.
+ */
+ fun stop(ctx: ActorContext<T>) {
+ behavior = start(ctx, StoppedBehavior.narrow())
+ }
+
+ /**
+ * Replace the current behavior with the specified new behavior.
+ *
+ * @param ctx The [ActorContext] to run the behavior in.
+ * @param next The behavior to replace the current behavior with.
+ */
+ fun become(ctx: ActorContext<T>, next: Behavior<T>) {
+ this.behavior = canonicalize(ctx, behavior, next)
+ }
+
+ /**
+ * Propagate special states of the wrapper [Behavior] to the specified [Behavior]. This means
+ * that if the behavior of this interpreter is stopped or unhandled, this will be propagated.
+ *
+ * @param behavior The [Behavior] to map.
+ * @return Either the specified [Behavior] or the propagated special objects.
+ */
+ fun propagate(behavior: Behavior<T>): Behavior<T> =
+ if (this.behavior.isUnhandled || !this.behavior.isAlive)
+ this.behavior
+ else
+ behavior
+
+ /**
+ * Interpret the given message of type [T] using the current active behavior.
+ *
+ * @return `true` if the message was handled by the active behavior, `false` otherwise.
+ */
+ fun interpretMessage(ctx: ActorContext<T>, msg: T): Boolean = interpret(ctx, msg, false)
+
+ /**
+ * Interpret the given [Signal] using the current active behavior.
+ *
+ * @return `true` if the signal was handled by the active behavior, `false` otherwise.
+ */
+ fun interpretSignal(ctx: ActorContext<T>, signal: Signal): Boolean = interpret(ctx, signal, true)
+
+ /**
+ * Interpret the given message or signal using the current active behavior.
+ *
+ * @return `true` if the message or signal was handled by the active behavior, `false` otherwise.
+ */
+ private fun interpret(ctx: ActorContext<T>, msg: Any, isSignal: Boolean): Boolean =
+ if (isAlive) {
+ val next = when (val current = behavior) {
+ is DeferredBehavior<T> ->
+ throw IllegalStateException("Deferred [$current] should not be passed to interpreter")
+ is ReceivingBehavior<T> ->
+ if (isSignal)
+ current.receiveSignal(ctx, msg as Signal)
+ else
+ @Suppress("UNCHECKED_CAST")
+ current.receive(ctx, msg as T)
+ is SameBehavior, is UnhandledBehavior ->
+ throw IllegalStateException("Cannot execute with [$current] as behavior")
+ is StoppedBehavior -> current
+ }
+
+ val unhandled = next.isUnhandled
+ behavior = canonicalize(ctx, behavior, next)
+ !unhandled
+ } else {
+ false
+ }
+
+ /**
+ * Validate whether the given [Behavior] can be used as initial behavior. Throw an [IllegalArgumentException] if
+ * the [Behavior] is not valid.
+ *
+ * @param behavior The behavior to validate.
+ */
+ private fun validateAsInitial(behavior: Behavior<T>): Behavior<T> =
+ when (behavior) {
+ is SameBehavior, is UnhandledBehavior ->
+ throw IllegalArgumentException("Cannot use [$behavior] as initial behavior")
+ else -> behavior
+ }
+
+ /**
+ * Helper methods to properly manage the special, canned behavior objects. It highly recommended to use the
+ * [BehaviorInterpreter] instead to properly manage the life-cycles of the behavior objects.
+ */
+ companion object {
+ /**
+ * Start the initial behavior of an actor in the specified [ActorContext].
+ *
+ * This will activate the initial behavior and canonicalize the resulting behavior.
+ *
+ * @param ctx The [ActorContext] to start the behavior in.
+ * @param behavior The initial behavior to start.
+ * @return The behavior that has been started.
+ */
+ tailrec fun <T : Any> start(ctx: ActorContext<T>, behavior: Behavior<T>): Behavior<T> =
+ when (behavior) {
+ is DeferredBehavior<T> -> start(ctx, behavior(ctx))
+ else -> behavior
+ }
+
+ /**
+ * Given a possibly special behavior (same or unhandled) and a "current" behavior (which defines the meaning of
+ * encountering a `same` behavior) this method computes the next behavior, suitable for passing a message or
+ * signal.
+ *
+ * @param ctx The context in which the actor runs.
+ * @param current The actor's current behavior.
+ * @param next The actor's next behavior.
+ * @return The actor's canonicalized next behavior.
+ */
+ tailrec fun <T : Any> canonicalize(ctx: ActorContext<T>, current: Behavior<T>, next: Behavior<T>): Behavior<T> =
+ when (next) {
+ is SameBehavior, current -> current
+ is UnhandledBehavior -> current
+ is DeferredBehavior<T> -> canonicalize(ctx, current, next(ctx))
+ else -> next
+ }
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt
new file mode 100644
index 00000000..c3346bdf
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt
@@ -0,0 +1,182 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2018 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal
+
+import com.atlarge.odcsim.ActorContext
+import com.atlarge.odcsim.ActorRef
+import com.atlarge.odcsim.ActorSystem
+import com.atlarge.odcsim.Behavior
+import com.atlarge.odcsim.Duration
+import com.atlarge.odcsim.Instant
+import com.atlarge.odcsim.ReceivingBehavior
+import com.atlarge.odcsim.Signal
+import com.atlarge.odcsim.coroutines.SuspendingActorContext
+import com.atlarge.odcsim.coroutines.SuspendingBehavior
+import com.atlarge.odcsim.coroutines.suspendWithBehavior
+import com.atlarge.odcsim.empty
+import com.atlarge.odcsim.receiveMessage
+import com.atlarge.odcsim.receiveSignal
+import kotlin.coroutines.Continuation
+import kotlin.coroutines.CoroutineContext
+import kotlin.coroutines.resume
+import kotlin.coroutines.startCoroutine
+import org.slf4j.Logger
+
+/**
+ * This interface exposes internal functionality provided by [SuspendingBehaviorImpl] on [SuspendingActorContext] to
+ * control the active behavior of the coroutine.
+ */
+interface SuspendingActorContextImpl<T : Any> : SuspendingActorContext<T> {
+ /**
+ * The current active behavior
+ */
+ val behavior: Behavior<T>
+
+ /**
+ * Replace the current active behavior with the specified new behavior.
+ *
+ * @param next The behavior to replace the current behavior with.
+ */
+ fun become(next: Behavior<T>)
+}
+
+/**
+ * Implementation of [SuspendingBehavior] class that maps the suspending method calls to the [Behavior]
+ * interface.
+ * This implementation uses the fact that each actor is thread-safe (as it processes its mailbox sequentially).
+ */
+internal class SuspendingBehaviorImpl<T : Any>(
+ private var actorContext: ActorContext<T>,
+ initialBehavior: SuspendingBehavior<T>
+) : ReceivingBehavior<T>(), SuspendingActorContextImpl<T> {
+
+ /**
+ * The next behavior to use.
+ */
+ private var next: Behavior<T> = this
+
+ /**
+ * The [BehaviorInterpreter] to wrap the suspending behavior.
+ */
+ private val interpreter = BehaviorInterpreter(initialBehavior)
+
+ override fun receive(ctx: ActorContext<T>, msg: T): Behavior<T> {
+ this.actorContext = ctx
+ return interpreter.also { it.interpretMessage(ctx, msg) }.propagate(next)
+ }
+
+ override fun receiveSignal(ctx: ActorContext<T>, signal: Signal): Behavior<T> {
+ this.actorContext = ctx
+ return interpreter.also { it.interpretSignal(ctx, signal) }.propagate(next)
+ }
+
+ override val self: ActorRef<T> get() = actorContext.self
+
+ override val time: Instant get() = actorContext.time
+
+ override val children: List<ActorRef<*>>
+ get() = actorContext.children
+
+ override val system: ActorSystem<*>
+ get() = actorContext.system
+
+ override val log: Logger
+ get() = actorContext.log
+
+ override fun getChild(name: String): ActorRef<*>? = actorContext.getChild(name)
+
+ override fun <U : Any> send(ref: ActorRef<U>, msg: U, after: Duration) = actorContext.send(ref, msg, after)
+
+ override fun <U : Any> spawn(behavior: Behavior<U>, name: String) = actorContext.spawn(behavior, name)
+
+ override fun <U : Any> spawnAnonymous(behavior: Behavior<U>) = actorContext.spawnAnonymous(behavior)
+
+ override fun stop(child: ActorRef<*>) = actorContext.stop(child)
+
+ override fun watch(target: ActorRef<*>) = actorContext.watch(target)
+
+ override fun unwatch(target: ActorRef<*>) = actorContext.unwatch(target)
+
+ override fun sync(target: ActorRef<*>) = actorContext.sync(target)
+
+ override fun unsync(target: ActorRef<*>) = actorContext.unsync(target)
+
+ override fun isSync(target: ActorRef<*>): Boolean = actorContext.isSync(target)
+
+ override suspend fun receive(): T = suspendWithBehavior<T, T> { cont, next ->
+ receiveMessage { msg ->
+ cont.resume(msg)
+ next()
+ }
+ }
+
+ override suspend fun receiveSignal(): Signal = suspendWithBehavior<T, Signal> { cont, next ->
+ receiveSignal { _, signal ->
+ cont.resume(signal)
+ next()
+ }
+ }
+
+ override val behavior: Behavior<T> get() = interpreter.behavior
+
+ override fun become(next: Behavior<T>) {
+ interpreter.become(actorContext, next)
+ }
+
+ override val key: CoroutineContext.Key<*> = SuspendingActorContext.Key
+
+ /**
+ * Start the suspending behavior.
+ */
+ internal fun start(): Behavior<T> {
+ val behavior = interpreter.behavior as SuspendingBehavior<T>
+ val block = suspend { behavior(this) }
+ interpreter.become(actorContext, empty())
+ block.startCoroutine(SuspendingBehaviorImplContinuation())
+ return next
+ }
+
+ /**
+ * Stop the suspending behavior.
+ */
+ private fun stop() {
+ this.interpreter.stop(actorContext)
+ }
+
+ /**
+ * The continuation of suspending behavior.
+ */
+ private inner class SuspendingBehaviorImplContinuation : Continuation<Behavior<T>> {
+ override val context = this@SuspendingBehaviorImpl
+
+ override fun resumeWith(result: Result<Behavior<T>>) {
+ if (result.isSuccess) {
+ next = result.getOrNull()!!
+ } else if (result.isFailure) {
+ throw result.exceptionOrNull()!!
+ }
+ }
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt
new file mode 100644
index 00000000..24c3a9d5
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt
@@ -0,0 +1,74 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal
+
+import com.atlarge.odcsim.ActorContext
+import com.atlarge.odcsim.Behavior
+import com.atlarge.odcsim.StashBuffer
+import java.util.ArrayDeque
+
+/**
+ * Internal implementation of the [StashBuffer] interface.
+ */
+internal class StashBufferImpl<T : Any>(private val capacity: Int) : StashBuffer<T> {
+ /**
+ * The internal queue used to store the messages.
+ */
+ private val queue = ArrayDeque<T>(capacity)
+
+ override val head: T
+ get() = queue.first
+
+ override val isEmpty: Boolean
+ get() = queue.isEmpty()
+
+ override val isFull: Boolean
+ get() = size > capacity
+
+ override val size: Int
+ get() = queue.size
+
+ override fun forEach(block: (T) -> Unit) {
+ queue.toList().forEach(block)
+ }
+
+ override fun stash(msg: T) {
+ queue.add(msg)
+ }
+
+ override fun unstashAll(ctx: ActorContext<T>, behavior: Behavior<T>): Behavior<T> {
+ val messages = queue.toList()
+ queue.clear()
+
+ val interpreter = BehaviorInterpreter<T>(behavior)
+ interpreter.start(ctx)
+
+ for (message in messages) {
+ interpreter.interpretMessage(ctx, message)
+ }
+
+ return interpreter.behavior
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt
new file mode 100644
index 00000000..22bec507
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt
@@ -0,0 +1,122 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal
+
+import com.atlarge.odcsim.ActorContext
+import com.atlarge.odcsim.Duration
+import com.atlarge.odcsim.Signal
+import com.atlarge.odcsim.TimerScheduler
+
+/**
+ * Implementation of [TimerScheduler] that uses the actor's [ActorContext] to provide timer functionality.
+ *
+ * @property ctx The actor context to use.
+ */
+internal class TimerSchedulerImpl<T : Any>(private val ctx: ActorContext<T>) : TimerScheduler<T> {
+ private val timers = mutableMapOf<Any, Timer<T>>()
+
+ override fun cancel(key: Any) {
+ val timer = timers[key] ?: return
+ ctx.log.debug("Cancel timer [{}] with generation [{}]", timer.key, timer.generation)
+ timers -= timer.key
+ }
+
+ override fun cancelAll() {
+ ctx.log.debug("Cancel all timers")
+ timers.clear()
+ }
+
+ override fun isTimerActive(key: Any): Boolean = timers.containsKey(key)
+
+ override fun startPeriodicTimer(key: Any, msg: T, interval: Duration) {
+ startTimer(key, msg, interval, true)
+ }
+
+ override fun startSingleTimer(key: Any, msg: T, delay: Duration) {
+ startTimer(key, msg, delay, false)
+ }
+
+ override fun every(key: Any, interval: Duration, block: () -> Unit) {
+ @Suppress("UNCHECKED_CAST")
+ startTimer(key, Block(block) as T, interval, true)
+ }
+
+ override fun after(key: Any, delay: Duration, block: () -> Unit) {
+ @Suppress("UNCHECKED_CAST")
+ startTimer(key, Block(block) as T, delay, false)
+ }
+
+ private fun startTimer(key: Any, msg: T, duration: Duration, repeat: Boolean) {
+ val timer = timers.getOrPut(key) { Timer(key) }
+ timer.duration = duration
+ timer.generation += 1
+ timer.msg = msg
+ timer.repeat = repeat
+ ctx.sendSignal(ctx.self, TimerSignal(key, timer.generation), duration)
+ ctx.log.debug("Start timer [{}] with generation [{}]", key, timer.generation)
+ }
+
+ fun interceptTimerSignal(signal: TimerSignal): T? {
+ val timer = timers[signal.key]
+
+ if (timer == null) {
+ // Message was from canceled timer that was already enqueued
+ ctx.log.debug("Received timer [{}] that has been removed, discarding", signal.key)
+ return null
+ } else if (signal.generation != timer.generation) {
+ // Message was from an old timer that was enqueued before canceled
+ ctx.log.debug("Received timer [{}] from old generation [{}], expected generation [{}], discarding",
+ signal.key, signal.generation, timer.generation)
+ }
+
+ if (!timer.repeat) {
+ timers -= timer.key
+ } else {
+ ctx.sendSignal(ctx.self, signal, timer.duration)
+ }
+
+ val msg = timer.msg
+
+ if (msg is Block) {
+ msg()
+ return null
+ }
+
+ return msg
+ }
+
+ data class Timer<T : Any>(val key: Any) {
+ var duration: Duration = 0.0
+ var repeat: Boolean = false
+ var generation: Int = 0
+ lateinit var msg: T
+ }
+
+ data class TimerSignal(val key: Any, val generation: Int) : Signal
+
+ data class Block(val block: () -> Unit) {
+ operator fun invoke() = block()
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt
new file mode 100644
index 00000000..bf50b5e8
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt
@@ -0,0 +1,567 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal.logging
+
+import com.atlarge.odcsim.ActorContext
+import org.slf4j.Logger
+import org.slf4j.Marker
+import org.slf4j.helpers.MessageFormatter
+import org.slf4j.spi.LocationAwareLogger
+
+/**
+ * An actor-specific [Logger] implementation that is aware of the calling location.
+ *
+ * @param ctx The owning [ActorContext] of this logger.
+ * @param delegate The [LocationAwareLogger] to delegate the messages to.
+ */
+internal class LocationAwareLoggerImpl(
+ ctx: ActorContext<*>,
+ private val delegate: LocationAwareLogger
+) : LoggerImpl(ctx), Logger by delegate {
+ /**
+ * The fully qualified name of this class.
+ */
+ private val fqcn = LocationAwareLoggerImpl::class.java.name
+
+ override fun trace(format: String?, arg: Any?) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(null, fqcn, LocationAwareLogger.TRACE_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun trace(format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(null, fqcn, LocationAwareLogger.TRACE_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun trace(format: String?, argArray: Array<Any?>) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(null, fqcn, LocationAwareLogger.TRACE_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun trace(msg: String?, t: Throwable?) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.TRACE_INT, msg, null, t)
+ }
+ }
+
+ override fun trace(marker: Marker?, msg: String?) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.TRACE_INT, msg, null, null)
+ }
+ }
+
+ override fun trace(marker: Marker?, format: String?, arg: Any?) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(marker, fqcn, LocationAwareLogger.TRACE_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun trace(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(marker, fqcn, LocationAwareLogger.TRACE_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun trace(marker: Marker?, format: String?, argArray: Array<Any?>) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(marker, fqcn, LocationAwareLogger.TRACE_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun trace(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!delegate.isTraceEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.TRACE_INT, msg, null, t)
+ }
+ }
+
+ override fun debug(msg: String?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.DEBUG_INT, msg, null, null)
+ }
+ }
+
+ override fun debug(format: String?, arg: Any?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(null, fqcn, LocationAwareLogger.DEBUG_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun debug(format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(null, fqcn, LocationAwareLogger.DEBUG_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun debug(format: String?, argArray: Array<Any?>) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ val ft = MessageFormatter.arrayFormat(format, argArray)
+ delegate.log(null, fqcn, LocationAwareLogger.DEBUG_INT, ft.message, ft.argArray, ft.throwable)
+ }
+ }
+
+ override fun debug(msg: String?, t: Throwable?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.DEBUG_INT, msg, null, t)
+ }
+ }
+
+ override fun debug(marker: Marker?, msg: String?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.DEBUG_INT, msg, null, null)
+ }
+ }
+
+ override fun debug(marker: Marker?, format: String?, arg: Any?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ val ft = MessageFormatter.format(format, arg)
+ delegate.log(marker, fqcn, LocationAwareLogger.DEBUG_INT, ft.message, ft.argArray, ft.throwable)
+ }
+ }
+
+ override fun debug(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(marker, fqcn, LocationAwareLogger.DEBUG_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun debug(marker: Marker?, format: String?, argArray: Array<Any?>) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ val ft = MessageFormatter.arrayFormat(format, argArray)
+ delegate.log(marker, fqcn, LocationAwareLogger.DEBUG_INT, ft.message, argArray, ft.throwable)
+ }
+ }
+
+ override fun debug(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!delegate.isDebugEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.DEBUG_INT, msg, null, t)
+ }
+ }
+
+ override fun info(msg: String?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.INFO_INT, msg, null, null)
+ }
+ }
+
+ override fun info(format: String?, arg: Any?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(null, fqcn, LocationAwareLogger.INFO_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun info(format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(null, fqcn, LocationAwareLogger.INFO_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun info(format: String?, argArray: Array<Any?>) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(null, fqcn, LocationAwareLogger.INFO_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun info(msg: String?, t: Throwable?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.INFO_INT, msg, null, t)
+ }
+ }
+
+ override fun info(marker: Marker?, msg: String?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.INFO_INT, msg, null, null)
+ }
+ }
+
+ override fun info(marker: Marker?, format: String?, arg: Any?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(marker, fqcn, LocationAwareLogger.INFO_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun info(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(marker, fqcn, LocationAwareLogger.INFO_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun info(marker: Marker?, format: String?, argArray: Array<Any?>) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(marker, fqcn, LocationAwareLogger.INFO_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun info(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!delegate.isInfoEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.INFO_INT, msg, null, t)
+ }
+ }
+
+ override fun warn(msg: String?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.WARN_INT, msg, null, null)
+ }
+ }
+
+ override fun warn(format: String?, arg: Any?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(null, fqcn, LocationAwareLogger.WARN_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun warn(format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(null, fqcn, LocationAwareLogger.WARN_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun warn(format: String?, argArray: Array<Any?>) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(null, fqcn, LocationAwareLogger.WARN_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun warn(msg: String?, t: Throwable?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.WARN_INT, msg, null, t)
+ }
+ }
+
+ override fun warn(marker: Marker?, msg: String?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.WARN_INT, msg, null, null)
+ }
+ }
+
+ override fun warn(marker: Marker?, format: String?, arg: Any?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(marker, fqcn, LocationAwareLogger.WARN_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun warn(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(marker, fqcn, LocationAwareLogger.WARN_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun warn(marker: Marker?, format: String?, argArray: Array<Any?>) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(marker, fqcn, LocationAwareLogger.WARN_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun warn(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!delegate.isWarnEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.WARN_INT, msg, null, t)
+ }
+ }
+
+ override fun error(msg: String?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.ERROR_INT, msg, null, null)
+ }
+ }
+
+ override fun error(format: String?, arg: Any?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(null, fqcn, LocationAwareLogger.ERROR_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun error(format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(null, fqcn, LocationAwareLogger.ERROR_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun error(format: String?, argArray: Array<Any?>) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(null, fqcn, LocationAwareLogger.ERROR_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun error(msg: String?, t: Throwable?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(null, fqcn, LocationAwareLogger.ERROR_INT, msg, null, t)
+ }
+ }
+
+ override fun error(marker: Marker?, msg: String?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.ERROR_INT, msg, null, null)
+ }
+ }
+
+ override fun error(marker: Marker?, format: String?, arg: Any?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg).message
+ delegate.log(marker, fqcn, LocationAwareLogger.ERROR_INT, formattedMessage, arrayOf(arg), null)
+ }
+ }
+
+ override fun error(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.format(format, arg1, arg2).message
+ delegate.log(marker, fqcn, LocationAwareLogger.ERROR_INT, formattedMessage, arrayOf(arg1, arg2), null)
+ }
+ }
+
+ override fun error(marker: Marker?, format: String?, argArray: Array<Any?>) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ val formattedMessage = MessageFormatter.arrayFormat(format, argArray).message
+ delegate.log(marker, fqcn, LocationAwareLogger.ERROR_INT, formattedMessage, argArray, null)
+ }
+ }
+
+ override fun error(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!delegate.isErrorEnabled) {
+ return
+ }
+
+ withMdc {
+ delegate.log(marker, fqcn, LocationAwareLogger.ERROR_INT, msg, null, t)
+ }
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt
new file mode 100644
index 00000000..999e30e6
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt
@@ -0,0 +1,440 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal.logging
+
+import com.atlarge.odcsim.ActorContext
+import org.slf4j.Logger
+import org.slf4j.Marker
+
+/**
+ * A [Logger] implementation that is not aware of the calling location.
+ *
+ * @param ctx The owning [ActorContext] of this logger.
+ * @param delegate The [Logger] to delegate the messages to.
+ */
+internal class LocationIgnorantLoggerImpl(
+ ctx: ActorContext<*>,
+ private val delegate: Logger
+) : LoggerImpl(ctx), Logger by delegate {
+ override fun warn(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(marker, format, arg1, arg2) }
+ }
+
+ override fun warn(format: String?, arg1: Any?, arg2: Any?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(format, arg1, arg2) }
+ }
+
+ override fun warn(msg: String?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(msg) }
+ }
+
+ override fun warn(marker: Marker?, format: String?, arg: Any?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(marker, format, arg) }
+ }
+
+ override fun warn(marker: Marker?, format: String?, vararg arguments: Any?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(marker, format, arguments) }
+ }
+
+ override fun warn(format: String?, arg: Any?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(format, arg) }
+ }
+
+ override fun warn(marker: Marker?, msg: String?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(marker, msg) }
+ }
+
+ override fun warn(msg: String?, t: Throwable?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(msg, t) }
+ }
+
+ override fun warn(format: String?, vararg arguments: Any?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(format, *arguments) }
+ }
+
+ override fun warn(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!isWarnEnabled) {
+ return
+ }
+
+ withMdc { delegate.warn(marker, msg, t) }
+ }
+
+ override fun info(marker: Marker?, format: String?, vararg arguments: Any?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(marker, format, *arguments) }
+ }
+
+ override fun info(format: String?, arg: Any?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(format, arg) }
+ }
+
+ override fun info(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(marker, msg, t) }
+ }
+
+ override fun info(msg: String?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(msg) }
+ }
+
+ override fun info(format: String?, vararg arguments: Any?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(format, *arguments) }
+ }
+
+ override fun info(format: String?, arg1: Any?, arg2: Any?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(format, arg1, arg2) }
+ }
+
+ override fun info(marker: Marker?, msg: String?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(marker, msg) }
+ }
+
+ override fun info(marker: Marker?, format: String?, arg: Any?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(marker, format, arg) }
+ }
+
+ override fun info(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(marker, format, arg1, arg2) }
+ }
+
+ override fun info(msg: String?, t: Throwable?) {
+ if (!isInfoEnabled) {
+ return
+ }
+
+ withMdc { delegate.info(msg, t) }
+ }
+
+ override fun error(msg: String?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(msg) }
+ }
+
+ override fun error(marker: Marker?, msg: String?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(marker, msg) }
+ }
+
+ override fun error(format: String?, vararg arguments: Any?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(format, *arguments) }
+ }
+
+ override fun error(format: String?, arg: Any?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(format, arg) }
+ }
+
+ override fun error(msg: String?, t: Throwable?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(msg, t) }
+ }
+
+ override fun error(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(marker, format, arg1, arg2) }
+ }
+
+ override fun error(marker: Marker?, format: String?, vararg arguments: Any?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(marker, format, *arguments) }
+ }
+
+ override fun error(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(marker, msg, t) }
+ }
+
+ override fun error(format: String?, arg1: Any?, arg2: Any?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(format, arg1, arg2) }
+ }
+
+ override fun error(marker: Marker?, format: String?, arg: Any?) {
+ if (!isErrorEnabled) {
+ return
+ }
+
+ withMdc { delegate.error(marker, format, arg) }
+ }
+
+ override fun debug(format: String?, vararg arguments: Any?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(format, *arguments) }
+ }
+
+ override fun debug(format: String?, arg1: Any?, arg2: Any?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(format, arg1, arg2) }
+ }
+
+ override fun debug(msg: String?, t: Throwable?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(msg, t) }
+ }
+
+ override fun debug(format: String?, arg: Any?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(format, arg) }
+ }
+
+ override fun debug(marker: Marker?, msg: String?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(marker, msg) }
+ }
+
+ override fun debug(msg: String?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(msg) }
+ }
+
+ override fun debug(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(marker, msg, t) }
+ }
+
+ override fun debug(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(marker, format, arg1, arg2) }
+ }
+
+ override fun debug(marker: Marker?, format: String?, arg: Any?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(marker, format, arg) }
+ }
+
+ override fun debug(marker: Marker?, format: String?, vararg arguments: Any?) {
+ if (!isDebugEnabled) {
+ return
+ }
+
+ withMdc { delegate.debug(marker, format, *arguments) }
+ }
+
+ override fun trace(format: String?, arg: Any?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(format, arg) }
+ }
+
+ override fun trace(marker: Marker?, msg: String?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(marker, msg) }
+ }
+
+ override fun trace(msg: String?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(msg) }
+ }
+
+ override fun trace(msg: String?, t: Throwable?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(msg, t) }
+ }
+
+ override fun trace(format: String?, arg1: Any?, arg2: Any?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(format, arg1, arg2) }
+ }
+
+ override fun trace(marker: Marker?, format: String?, arg1: Any?, arg2: Any?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(marker, format, arg1, arg2) }
+ }
+
+ override fun trace(marker: Marker?, format: String?, arg: Any?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(marker, format, arg) }
+ }
+
+ override fun trace(marker: Marker?, format: String?, vararg argArray: Any?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(marker, format, *argArray) }
+ }
+
+ override fun trace(marker: Marker?, msg: String?, t: Throwable?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(marker, msg, t) }
+ }
+
+ override fun trace(format: String?, vararg arguments: Any?) {
+ if (!isTraceEnabled) {
+ return
+ }
+
+ withMdc { delegate.trace(format, *arguments) }
+ }
+}
diff --git a/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt
new file mode 100644
index 00000000..f971f08d
--- /dev/null
+++ b/odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt
@@ -0,0 +1,77 @@
+/*
+ * MIT License
+ *
+ * Copyright (c) 2019 atlarge-research
+ *
+ * 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.
+ */
+
+package com.atlarge.odcsim.internal.logging
+
+import com.atlarge.odcsim.ActorContext
+import org.slf4j.Logger
+import org.slf4j.LoggerFactory
+import org.slf4j.MDC
+import org.slf4j.spi.LocationAwareLogger
+
+/**
+ * An actor-specific [Logger] implementation.
+ *
+ * @param ctx The owning [ActorContext] of this logger.
+ */
+abstract class LoggerImpl internal constructor(protected val ctx: ActorContext<*>) : Logger {
+ /**
+ * Configure [MDC] with actor-specific information.
+ */
+ protected inline fun withMdc(block: () -> Unit) {
+ MDC.put(MDC_ACTOR_SYSTEM, ctx.system.name)
+ MDC.put(MDC_ACTOR_TIME, String.format("%.2f", ctx.time))
+ MDC.put(MDC_ACTOR_REF, ctx.self.path.toString())
+ try {
+ block()
+ } finally {
+ MDC.remove(MDC_ACTOR_SYSTEM)
+ MDC.remove(MDC_ACTOR_TIME)
+ MDC.remove(MDC_ACTOR_REF)
+ }
+ }
+
+ /**
+ * Mapped Diagnostic Context (MDC) attribute names.
+ */
+ companion object {
+ val MDC_ACTOR_SYSTEM = "actor.system"
+ val MDC_ACTOR_TIME = "actor.time"
+ val MDC_ACTOR_REF = "actor.ref"
+
+ /**
+ * Create a [Logger] for the specified [ActorContext].
+ *
+ * @param ctx The actor context to create the logger for.
+ */
+ operator fun invoke(ctx: ActorContext<*>): Logger {
+ val logger = LoggerFactory.getLogger(ctx.javaClass)
+ return if (logger is LocationAwareLogger) {
+ LocationAwareLoggerImpl(ctx, logger)
+ } else {
+ LocationIgnorantLoggerImpl(ctx, logger)
+ }
+ }
+ }
+}