From ce952cf3f27c154e06cfa56ca1ad7db9ba3eac7c Mon Sep 17 00:00:00 2001 From: Fabian Mastenbroek Date: Fri, 29 Nov 2019 16:05:09 +0100 Subject: 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. --- odcsim/odcsim-api/build.gradle.kts | 40 ++ .../main/kotlin/com/atlarge/odcsim/ActorContext.kt | 175 +++++++ .../main/kotlin/com/atlarge/odcsim/ActorPath.kt | 142 ++++++ .../src/main/kotlin/com/atlarge/odcsim/ActorRef.kt | 50 ++ .../main/kotlin/com/atlarge/odcsim/ActorSystem.kt | 76 +++ .../com/atlarge/odcsim/ActorSystemFactory.kt | 38 ++ .../src/main/kotlin/com/atlarge/odcsim/Behavior.kt | 193 +++++++ .../main/kotlin/com/atlarge/odcsim/Behaviors.kt | 223 ++++++++ .../src/main/kotlin/com/atlarge/odcsim/Envelope.kt | 57 +++ .../src/main/kotlin/com/atlarge/odcsim/Signals.kt | 60 +++ .../main/kotlin/com/atlarge/odcsim/StashBuffer.kt | 88 ++++ .../src/main/kotlin/com/atlarge/odcsim/Time.kt | 75 +++ .../kotlin/com/atlarge/odcsim/TimerScheduler.kt | 92 ++++ .../com/atlarge/odcsim/coroutines/Behavior.kt | 116 +++++ .../com/atlarge/odcsim/coroutines/dsl/Receive.kt | 66 +++ .../com/atlarge/odcsim/coroutines/dsl/Timeout.kt | 42 ++ .../com/atlarge/odcsim/internal/ActorContext.kt | 43 ++ .../kotlin/com/atlarge/odcsim/internal/Behavior.kt | 48 ++ .../atlarge/odcsim/internal/BehaviorInterpreter.kt | 201 ++++++++ .../com/atlarge/odcsim/internal/Coroutines.kt | 182 +++++++ .../com/atlarge/odcsim/internal/StashBufferImpl.kt | 74 +++ .../atlarge/odcsim/internal/TimerSchedulerImpl.kt | 122 +++++ .../internal/logging/LocationAwareLoggerImpl.kt | 567 +++++++++++++++++++++ .../internal/logging/LocationIgnorantLoggerImpl.kt | 440 ++++++++++++++++ .../atlarge/odcsim/internal/logging/LoggerImpl.kt | 77 +++ .../kotlin/com/atlarge/odcsim/ActorPathTest.kt | 86 ++++ .../test/kotlin/com/atlarge/odcsim/BehaviorTest.kt | 77 +++ .../kotlin/com/atlarge/odcsim/CoroutinesTest.kt | 63 +++ odcsim/odcsim-core/build.gradle.kts | 40 -- .../main/kotlin/com/atlarge/odcsim/ActorContext.kt | 175 ------- .../main/kotlin/com/atlarge/odcsim/ActorPath.kt | 142 ------ .../src/main/kotlin/com/atlarge/odcsim/ActorRef.kt | 50 -- .../main/kotlin/com/atlarge/odcsim/ActorSystem.kt | 76 --- .../com/atlarge/odcsim/ActorSystemFactory.kt | 38 -- .../src/main/kotlin/com/atlarge/odcsim/Behavior.kt | 193 ------- .../main/kotlin/com/atlarge/odcsim/Behaviors.kt | 223 -------- .../src/main/kotlin/com/atlarge/odcsim/Envelope.kt | 57 --- .../src/main/kotlin/com/atlarge/odcsim/Signals.kt | 60 --- .../main/kotlin/com/atlarge/odcsim/StashBuffer.kt | 88 ---- .../src/main/kotlin/com/atlarge/odcsim/Time.kt | 75 --- .../kotlin/com/atlarge/odcsim/TimerScheduler.kt | 92 ---- .../com/atlarge/odcsim/coroutines/Behavior.kt | 116 ----- .../com/atlarge/odcsim/coroutines/dsl/Receive.kt | 66 --- .../com/atlarge/odcsim/coroutines/dsl/Timeout.kt | 42 -- .../com/atlarge/odcsim/internal/ActorContext.kt | 43 -- .../kotlin/com/atlarge/odcsim/internal/Behavior.kt | 48 -- .../atlarge/odcsim/internal/BehaviorInterpreter.kt | 201 -------- .../com/atlarge/odcsim/internal/Coroutines.kt | 182 ------- .../com/atlarge/odcsim/internal/StashBufferImpl.kt | 74 --- .../atlarge/odcsim/internal/TimerSchedulerImpl.kt | 122 ----- .../internal/logging/LocationAwareLoggerImpl.kt | 567 --------------------- .../internal/logging/LocationIgnorantLoggerImpl.kt | 440 ---------------- .../atlarge/odcsim/internal/logging/LoggerImpl.kt | 77 --- .../kotlin/com/atlarge/odcsim/ActorPathTest.kt | 86 ---- .../test/kotlin/com/atlarge/odcsim/BehaviorTest.kt | 77 --- .../kotlin/com/atlarge/odcsim/CoroutinesTest.kt | 63 --- odcsim/odcsim-engine-omega/build.gradle.kts | 2 +- odcsim/odcsim-engine-tests/build.gradle.kts | 2 +- 58 files changed, 3515 insertions(+), 3515 deletions(-) create mode 100644 odcsim/odcsim-api/build.gradle.kts create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behavior.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Envelope.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Signals.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/Time.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt create mode 100644 odcsim/odcsim-api/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt create mode 100644 odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt create mode 100644 odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt create mode 100644 odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt delete mode 100644 odcsim/odcsim-core/build.gradle.kts delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behavior.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Envelope.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Signals.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Time.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt delete mode 100644 odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt delete mode 100644 odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt delete mode 100644 odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt delete mode 100644 odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt (limited to 'odcsim') diff --git a/odcsim/odcsim-api/build.gradle.kts b/odcsim/odcsim-api/build.gradle.kts new file mode 100644 index 00000000..b17cac39 --- /dev/null +++ b/odcsim/odcsim-api/build.gradle.kts @@ -0,0 +1,40 @@ +/* + * MIT License + * + * Copyright (c) 2017 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. + */ + +description = "Framework for discrete event simulation in Kotlin" + +/* Build configuration */ +plugins { + `kotlin-library-convention` +} + +dependencies { + implementation(kotlin("stdlib")) + api("org.slf4j:slf4j-api:${Library.SLF4J}") + + testImplementation("org.junit.jupiter:junit-jupiter-api:${Library.JUNIT_JUPITER}") + testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Library.JUNIT_JUPITER}") + testImplementation("org.junit.platform:junit-platform-launcher:${Library.JUNIT_PLATFORM}") + testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0") +} 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 { + /** + * The identity of the actor, bound to the lifecycle of this actor instance. + */ + val self: ActorRef + + /** + * A view of the children of this actor. + */ + val children: List> + + /** + * 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 send(ref: ActorRef, 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 spawn(behavior: Behavior, name: String): ActorRef + + /** + * 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 spawnAnonymous(behavior: Behavior): ActorRef + + /** + * 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 ActorContext.unsafeCast(): ActorContext { + @Suppress("UNCHECKED_CAST") + return this as ActorContext +} 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, 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): 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 : Comparable>, 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 ActorRef.unsafeCast(): ActorRef { + @Suppress("UNCHECKED_CAST") + return this as ActorRef +} 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 : ActorRef { + /** + * 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 spawnSystem(behavior: Behavior, name: String): ActorRef +} 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 invoke(root: Behavior, name: String): ActorSystem +} 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 { + /** + * 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 narrow(): Behavior = 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 widen(transform: (U) -> T?): Behavior { + return wrap(this) { interpreter -> + receive { ctx, msg -> + val res = transform(msg) + @Suppress("UNCHECKED_CAST") + if (res == null || interpreter.interpretMessage(ctx as ActorContext, 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): Behavior = + wrap(this) { left -> + wrap(that) { right -> + object : ReceivingBehavior() { + override fun receive(ctx: ActorContext, msg: T): Behavior { + if (left.interpretMessage(ctx, msg)) { + return left.behavior + } else if (right.interpretMessage(ctx, msg)) { + return right.behavior + } + + return unhandled() + } + + override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { + 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 unsafeCast(): Behavior { + @Suppress("UNCHECKED_CAST") + return this as Behavior + } +} + +/** + * 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 : Behavior() { + /** + * 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): Behavior +} + +/** + * 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 : Behavior() { + /** + * 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, msg: T): Behavior = 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, signal: Signal): Behavior = unhandled() +} + +/** + * A flag to indicate whether a [Behavior] instance is still alive. + */ +val Behavior.isAlive get() = this !is StoppedBehavior + +/** + * A flag to indicate whether the last message/signal went unhandled. + */ +val Behavior.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() { + override fun toString() = "Stopped" +} + +/** + * A special [Behavior] object to signal that the actor wants to reuse its previous behavior. + */ +internal object SameBehavior : Behavior() { + override fun toString() = "Same" +} + +/** + * A special [Behavior] object that indicates that the last message or signal was not handled. + */ +internal object UnhandledBehavior : Behavior() { + 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 stopped(): Behavior = StoppedBehavior.unsafeCast() + +/** + * This [Behavior] is used to signal that this actor wants to reuse its previous behavior. + */ +fun same(): Behavior = 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 unhandled(): Behavior = UnhandledBehavior.unsafeCast() + +/** + * A factory for [Behavior]. Creation of the behavior instance is deferred until the actor is started. + */ +fun setup(block: (ActorContext) -> Behavior): Behavior { + return object : DeferredBehavior() { + override fun invoke(ctx: ActorContext): Behavior = block(ctx) + } +} + +/** + * A [Behavior] that ignores any incoming message or signal and keeps the same behavior. + */ +fun ignore(): Behavior = IgnoreBehavior.narrow() + +/** + * A [Behavior] that treats every incoming message or signal as unhandled. + */ +fun empty(): Behavior = EmptyBehavior.narrow() + +/** + * Construct a [Behavior] that reacts to incoming messages, provides access to the [ActorContext] and returns the + * actor's next behavior. + */ +fun receive(handler: (ActorContext, T) -> Behavior): Behavior { + return object : ReceivingBehavior() { + override fun receive(ctx: ActorContext, msg: T): Behavior = 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 receiveOf(crossinline handler: (ActorContext, U) -> Behavior): Behavior { + return object : ReceivingBehavior() { + override fun receive(ctx: ActorContext, msg: T): Behavior { + 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 receiveMessage(handler: (T) -> Behavior): Behavior { + return object : ReceivingBehavior() { + override fun receive(ctx: ActorContext, msg: T): Behavior = handler(msg) + } +} + +/** + * Construct a [Behavior] that reacts to incoming signals, provides access to the [ActorContext] and returns the + * actor's next behavior. + */ +fun receiveSignal(handler: (ActorContext, Signal) -> Behavior): Behavior { + return object : ReceivingBehavior() { + override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior = 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 wrap(behavior: Behavior, wrap: (BehaviorInterpreter) -> Behavior): Behavior { + return setup { ctx -> wrap(BehaviorInterpreter(behavior, ctx)) } +} + +/** + * Obtain a [TimerScheduler] for building a [Behavior] instance. + */ +fun withTimers(handler: (TimerScheduler) -> Behavior): Behavior { + return setup { ctx -> + val scheduler = TimerSchedulerImpl(ctx) + receiveSignal { _, 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 withTimeout(after: Duration, handler: (ActorContext) -> Behavior): Behavior = + 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 Behavior.join(that: Behavior): Behavior = + wrap(this) { left -> + wrap(that) { right -> + object : ReceivingBehavior() { + override fun receive(ctx: ActorContext, msg: T): Behavior { + 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, signal: Signal): Behavior { + 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 Behavior.widen(): Behavior = widen { + if (it is T) + it + else + null +} + +/** + * Keep the specified [Behavior] alive if it returns the stopped behavior. + */ +fun Behavior.keepAlive(): Behavior = + wrap(this) { interpreter -> + object : ReceivingBehavior() { + override fun receive(ctx: ActorContext, msg: T): Behavior { + if (interpreter.interpretMessage(ctx, msg)) { + return this + } + return empty() + } + + override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { + 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 : Comparable>, 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 { + /** + * 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, behavior: Behavior): Behavior + + companion object { + /** + * Construct a [StashBuffer] with the specified [capacity]. + * + * @param capacity The capacity of the buffer. + */ + operator fun invoke(capacity: Int): StashBuffer = 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 { + /** + * 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 : DeferredBehavior() { + /** + * 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): Behavior + + // Immediately transfer to implementation + override fun invoke(ctx: ActorContext): Behavior = 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 : ActorContext, 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> +} + +/** + * 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 suspendWithBehavior(block: (Continuation, () -> Behavior) -> Behavior): U = + suspendCoroutine { cont -> + @Suppress("UNCHECKED_CAST") + val ctx = cont.context[SuspendingActorContext] as? SuspendingActorContextImpl + ?: 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 actorContext(): SuspendingActorContext = + suspendCoroutineUninterceptedOrReturn { cont -> + @Suppress("UNCHECKED_CAST") + cont.context[SuspendingActorContext] as? SuspendingActorContext + ?: throw UnsupportedOperationException("Coroutine does not run inside SuspendingBehavior") + } + +/** + * Construct a [Behavior] that uses Kotlin coroutines functionality to handle incoming messages and signals. + */ +fun suspending(block: suspend (SuspendingActorContext) -> Behavior): Behavior { + return object : SuspendingBehavior() { + override suspend fun invoke(ctx: SuspendingActorContext) = 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 SuspendingActorContext.receiveOf(): U = + suspendWithBehavior { 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 SuspendingActorContext.ask( + ref: ActorRef, + after: Duration = 0.0, + transform: (ActorRef) -> 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 { 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, 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() { + override fun receive(ctx: ActorContext, msg: Any): Behavior = this + + override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior = this + + override fun toString() = "Ignore" +} + +/** + * A [Behavior] object that does not handle any message it receives. + */ +internal object EmptyBehavior : ReceivingBehavior() { + 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(initialBehavior: Behavior) { + /** + * The current [Behavior] instance. + */ + var behavior: Behavior = 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, ctx: ActorContext) : this(initialBehavior) { + start(ctx) + } + + /** + * Start the initial behavior. + * + * @param ctx The [ActorContext] to start the behavior in. + */ + fun start(ctx: ActorContext) { + 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) { + 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, next: Behavior) { + 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): Behavior = + 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, 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, 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, msg: Any, isSignal: Boolean): Boolean = + if (isAlive) { + val next = when (val current = behavior) { + is DeferredBehavior -> + throw IllegalStateException("Deferred [$current] should not be passed to interpreter") + is ReceivingBehavior -> + 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): Behavior = + 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 start(ctx: ActorContext, behavior: Behavior): Behavior = + when (behavior) { + is DeferredBehavior -> 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 canonicalize(ctx: ActorContext, current: Behavior, next: Behavior): Behavior = + when (next) { + is SameBehavior, current -> current + is UnhandledBehavior -> current + is DeferredBehavior -> 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 : SuspendingActorContext { + /** + * The current active behavior + */ + val behavior: Behavior + + /** + * Replace the current active behavior with the specified new behavior. + * + * @param next The behavior to replace the current behavior with. + */ + fun become(next: Behavior) +} + +/** + * 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( + private var actorContext: ActorContext, + initialBehavior: SuspendingBehavior +) : ReceivingBehavior(), SuspendingActorContextImpl { + + /** + * The next behavior to use. + */ + private var next: Behavior = this + + /** + * The [BehaviorInterpreter] to wrap the suspending behavior. + */ + private val interpreter = BehaviorInterpreter(initialBehavior) + + override fun receive(ctx: ActorContext, msg: T): Behavior { + this.actorContext = ctx + return interpreter.also { it.interpretMessage(ctx, msg) }.propagate(next) + } + + override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { + this.actorContext = ctx + return interpreter.also { it.interpretSignal(ctx, signal) }.propagate(next) + } + + override val self: ActorRef get() = actorContext.self + + override val time: Instant get() = actorContext.time + + override val children: List> + 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 send(ref: ActorRef, msg: U, after: Duration) = actorContext.send(ref, msg, after) + + override fun spawn(behavior: Behavior, name: String) = actorContext.spawn(behavior, name) + + override fun spawnAnonymous(behavior: Behavior) = 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 { cont, next -> + receiveMessage { msg -> + cont.resume(msg) + next() + } + } + + override suspend fun receiveSignal(): Signal = suspendWithBehavior { cont, next -> + receiveSignal { _, signal -> + cont.resume(signal) + next() + } + } + + override val behavior: Behavior get() = interpreter.behavior + + override fun become(next: Behavior) { + interpreter.become(actorContext, next) + } + + override val key: CoroutineContext.Key<*> = SuspendingActorContext.Key + + /** + * Start the suspending behavior. + */ + internal fun start(): Behavior { + val behavior = interpreter.behavior as SuspendingBehavior + 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> { + override val context = this@SuspendingBehaviorImpl + + override fun resumeWith(result: Result>) { + 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(private val capacity: Int) : StashBuffer { + /** + * The internal queue used to store the messages. + */ + private val queue = ArrayDeque(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, behavior: Behavior): Behavior { + val messages = queue.toList() + queue.clear() + + val interpreter = BehaviorInterpreter(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(private val ctx: ActorContext) : TimerScheduler { + private val timers = mutableMapOf>() + + 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(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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) { + 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) + } + } + } +} diff --git a/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt b/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt new file mode 100644 index 00000000..023d3efd --- /dev/null +++ b/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt @@ -0,0 +1,86 @@ +/* + * 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.junit.jupiter.api.Assertions.assertEquals +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +/** + * A test suite for the [ActorPath] class. + */ +@DisplayName("ActorPath") +class ActorPathTest { + /** + * Test whether an [ActorPath.Root] may only start with a slash. + */ + @Test + fun `root node may only start with a slash`() { + ActorPath.Root() // Assert slash at start + assertThrows { ActorPath.Root("abc/") } + } + + /** + * Test whether an [ActorPath.Child] disallows names with a slash. + */ + @Test + fun `child node should not allow name with a slash`() { + assertThrows { ActorPath.Child(ActorPath.Root(), "/") } + } + + /** + * Test whether a root node can have a custom name. + */ + @Test + fun `root node can have a custom name`() { + val name = "user" + assertEquals(name, ActorPath.Root(name).name) + } + + /** + * Test whether a child node can be created on a root. + */ + @Test + fun `child node can be created on a root`() { + val root = ActorPath.Root(name = "/user") + val child = root.child("child") + + assertEquals(root, child.parent) + assertEquals("child", child.name) + } + + /** + * Test whether a child node can be created on a child. + */ + @Test + fun `child node can be created on a child`() { + val root = ActorPath.Root(name = "/user").child("child") + val child = root.child("child") + + assertEquals(root, child.parent) + assertEquals("child", child.name) + } +} diff --git a/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt b/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt new file mode 100644 index 00000000..1eb4f3b9 --- /dev/null +++ b/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt @@ -0,0 +1,77 @@ +/* + * 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 com.atlarge.odcsim.internal.BehaviorInterpreter +import com.nhaarman.mockitokotlin2.mock +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test +import org.junit.jupiter.api.assertThrows + +/** + * Test suite for [Behavior] and [BehaviorInterpreter]. + */ +@DisplayName("Behavior") +class BehaviorTest { + /** + * Test whether we cannot start an actor with the [unhandled] behavior. + */ + @Test + fun `should not start with unhandled behavior`() { + val ctx = mock>() + val interpreter = BehaviorInterpreter(unhandled()) + assertThrows { interpreter.start(ctx) } + } + + /** + * Test whether we cannot start an actor with deferred unhandled behavior. + */ + @Test + fun `should not start with deferred unhandled behavior`() { + val ctx = mock>() + val interpreter = BehaviorInterpreter(setup { unhandled() }) + assertThrows { interpreter.start(ctx) } + } + + /** + * Test whether deferred behavior that returns [same] fails. + */ + @Test + fun `should not allow setup to return same`() { + val ctx = mock>() + val interpreter = BehaviorInterpreter(setup { same() }) + assertThrows { interpreter.start(ctx) } + } + + /** + * Test whether deferred behavior that returns [unhandled] fails. + */ + @Test + fun `should not allow setup to return unhandled`() { + val ctx = mock>() + val interpreter = BehaviorInterpreter(setup { unhandled() }) + assertThrows { interpreter.start(ctx) } + } +} diff --git a/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt b/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt new file mode 100644 index 00000000..b59c5ea7 --- /dev/null +++ b/odcsim/odcsim-api/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt @@ -0,0 +1,63 @@ +/* + * 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.coroutines.SuspendingBehavior +import com.atlarge.odcsim.coroutines.suspending +import com.atlarge.odcsim.internal.BehaviorInterpreter +import com.atlarge.odcsim.internal.EmptyBehavior +import com.nhaarman.mockitokotlin2.mock +import kotlin.coroutines.suspendCoroutine +import org.junit.jupiter.api.Assertions.assertTrue +import org.junit.jupiter.api.DisplayName +import org.junit.jupiter.api.Test + +/** + * Test suite for [SuspendingBehavior] using Kotlin Coroutines. + */ +@DisplayName("Coroutines") +internal class CoroutinesTest { + + @Test + fun `should immediately return new behavior`() { + val ctx = mock>() + val behavior = suspending { empty() } + val interpreter = BehaviorInterpreter(behavior) + interpreter.start(ctx) + assertTrue(interpreter.behavior as Behavior<*> is EmptyBehavior) + } + + @Test + fun `should be able to invoke regular suspend methods`() { + val ctx = mock>() + val behavior = suspending { + suspendCoroutine {} + stopped() + } + val interpreter = BehaviorInterpreter(behavior) + interpreter.start(ctx) + interpreter.interpretMessage(ctx, Unit) + } +} diff --git a/odcsim/odcsim-core/build.gradle.kts b/odcsim/odcsim-core/build.gradle.kts deleted file mode 100644 index b17cac39..00000000 --- a/odcsim/odcsim-core/build.gradle.kts +++ /dev/null @@ -1,40 +0,0 @@ -/* - * MIT License - * - * Copyright (c) 2017 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. - */ - -description = "Framework for discrete event simulation in Kotlin" - -/* Build configuration */ -plugins { - `kotlin-library-convention` -} - -dependencies { - implementation(kotlin("stdlib")) - api("org.slf4j:slf4j-api:${Library.SLF4J}") - - testImplementation("org.junit.jupiter:junit-jupiter-api:${Library.JUNIT_JUPITER}") - testRuntimeOnly("org.junit.jupiter:junit-jupiter-engine:${Library.JUNIT_JUPITER}") - testImplementation("org.junit.platform:junit-platform-launcher:${Library.JUNIT_PLATFORM}") - testImplementation("com.nhaarman.mockitokotlin2:mockito-kotlin:2.0.0") -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt deleted file mode 100644 index dc6ca7ec..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorContext.kt +++ /dev/null @@ -1,175 +0,0 @@ -/* - * 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 { - /** - * The identity of the actor, bound to the lifecycle of this actor instance. - */ - val self: ActorRef - - /** - * A view of the children of this actor. - */ - val children: List> - - /** - * 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 send(ref: ActorRef, 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 spawn(behavior: Behavior, name: String): ActorRef - - /** - * 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 spawnAnonymous(behavior: Behavior): ActorRef - - /** - * 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 ActorContext.unsafeCast(): ActorContext { - @Suppress("UNCHECKED_CAST") - return this as ActorContext -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt deleted file mode 100644 index a6c716a2..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorPath.kt +++ /dev/null @@ -1,142 +0,0 @@ -/* - * 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, 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): 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-core/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt deleted file mode 100644 index 45fc756e..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorRef.kt +++ /dev/null @@ -1,50 +0,0 @@ -/* - * 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 : Comparable>, 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 ActorRef.unsafeCast(): ActorRef { - @Suppress("UNCHECKED_CAST") - return this as ActorRef -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt deleted file mode 100644 index d65beebd..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystem.kt +++ /dev/null @@ -1,76 +0,0 @@ -/* - * 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 : ActorRef { - /** - * 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 spawnSystem(behavior: Behavior, name: String): ActorRef -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt deleted file mode 100644 index f59bc966..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/ActorSystemFactory.kt +++ /dev/null @@ -1,38 +0,0 @@ -/* - * 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 invoke(root: Behavior, name: String): ActorSystem -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behavior.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behavior.kt deleted file mode 100644 index 9ad7f83f..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behavior.kt +++ /dev/null @@ -1,193 +0,0 @@ -/* - * 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 { - /** - * 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 narrow(): Behavior = 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 widen(transform: (U) -> T?): Behavior { - return wrap(this) { interpreter -> - receive { ctx, msg -> - val res = transform(msg) - @Suppress("UNCHECKED_CAST") - if (res == null || interpreter.interpretMessage(ctx as ActorContext, 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): Behavior = - wrap(this) { left -> - wrap(that) { right -> - object : ReceivingBehavior() { - override fun receive(ctx: ActorContext, msg: T): Behavior { - if (left.interpretMessage(ctx, msg)) { - return left.behavior - } else if (right.interpretMessage(ctx, msg)) { - return right.behavior - } - - return unhandled() - } - - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - 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 unsafeCast(): Behavior { - @Suppress("UNCHECKED_CAST") - return this as Behavior - } -} - -/** - * 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 : Behavior() { - /** - * 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): Behavior -} - -/** - * 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 : Behavior() { - /** - * 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, msg: T): Behavior = 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, signal: Signal): Behavior = unhandled() -} - -/** - * A flag to indicate whether a [Behavior] instance is still alive. - */ -val Behavior.isAlive get() = this !is StoppedBehavior - -/** - * A flag to indicate whether the last message/signal went unhandled. - */ -val Behavior.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() { - override fun toString() = "Stopped" -} - -/** - * A special [Behavior] object to signal that the actor wants to reuse its previous behavior. - */ -internal object SameBehavior : Behavior() { - override fun toString() = "Same" -} - -/** - * A special [Behavior] object that indicates that the last message or signal was not handled. - */ -internal object UnhandledBehavior : Behavior() { - override fun toString() = "Unhandled" -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt deleted file mode 100644 index eac254ec..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Behaviors.kt +++ /dev/null @@ -1,223 +0,0 @@ -/* - * 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 stopped(): Behavior = StoppedBehavior.unsafeCast() - -/** - * This [Behavior] is used to signal that this actor wants to reuse its previous behavior. - */ -fun same(): Behavior = 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 unhandled(): Behavior = UnhandledBehavior.unsafeCast() - -/** - * A factory for [Behavior]. Creation of the behavior instance is deferred until the actor is started. - */ -fun setup(block: (ActorContext) -> Behavior): Behavior { - return object : DeferredBehavior() { - override fun invoke(ctx: ActorContext): Behavior = block(ctx) - } -} - -/** - * A [Behavior] that ignores any incoming message or signal and keeps the same behavior. - */ -fun ignore(): Behavior = IgnoreBehavior.narrow() - -/** - * A [Behavior] that treats every incoming message or signal as unhandled. - */ -fun empty(): Behavior = EmptyBehavior.narrow() - -/** - * Construct a [Behavior] that reacts to incoming messages, provides access to the [ActorContext] and returns the - * actor's next behavior. - */ -fun receive(handler: (ActorContext, T) -> Behavior): Behavior { - return object : ReceivingBehavior() { - override fun receive(ctx: ActorContext, msg: T): Behavior = 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 receiveOf(crossinline handler: (ActorContext, U) -> Behavior): Behavior { - return object : ReceivingBehavior() { - override fun receive(ctx: ActorContext, msg: T): Behavior { - 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 receiveMessage(handler: (T) -> Behavior): Behavior { - return object : ReceivingBehavior() { - override fun receive(ctx: ActorContext, msg: T): Behavior = handler(msg) - } -} - -/** - * Construct a [Behavior] that reacts to incoming signals, provides access to the [ActorContext] and returns the - * actor's next behavior. - */ -fun receiveSignal(handler: (ActorContext, Signal) -> Behavior): Behavior { - return object : ReceivingBehavior() { - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior = 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 wrap(behavior: Behavior, wrap: (BehaviorInterpreter) -> Behavior): Behavior { - return setup { ctx -> wrap(BehaviorInterpreter(behavior, ctx)) } -} - -/** - * Obtain a [TimerScheduler] for building a [Behavior] instance. - */ -fun withTimers(handler: (TimerScheduler) -> Behavior): Behavior { - return setup { ctx -> - val scheduler = TimerSchedulerImpl(ctx) - receiveSignal { _, 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 withTimeout(after: Duration, handler: (ActorContext) -> Behavior): Behavior = - 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 Behavior.join(that: Behavior): Behavior = - wrap(this) { left -> - wrap(that) { right -> - object : ReceivingBehavior() { - override fun receive(ctx: ActorContext, msg: T): Behavior { - 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, signal: Signal): Behavior { - 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 Behavior.widen(): Behavior = widen { - if (it is T) - it - else - null -} - -/** - * Keep the specified [Behavior] alive if it returns the stopped behavior. - */ -fun Behavior.keepAlive(): Behavior = - wrap(this) { interpreter -> - object : ReceivingBehavior() { - override fun receive(ctx: ActorContext, msg: T): Behavior { - if (interpreter.interpretMessage(ctx, msg)) { - return this - } - return empty() - } - - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - if (interpreter.interpretSignal(ctx, signal)) { - return this - } - - return empty() - } - } - } diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Envelope.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Envelope.kt deleted file mode 100644 index 3b73d52d..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Envelope.kt +++ /dev/null @@ -1,57 +0,0 @@ -/* - * 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 : Comparable>, 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-core/src/main/kotlin/com/atlarge/odcsim/Signals.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Signals.kt deleted file mode 100644 index 9b707348..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Signals.kt +++ /dev/null @@ -1,60 +0,0 @@ -/* - * 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-core/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt deleted file mode 100644 index 5d73d808..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/StashBuffer.kt +++ /dev/null @@ -1,88 +0,0 @@ -/* - * 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 { - /** - * 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, behavior: Behavior): Behavior - - companion object { - /** - * Construct a [StashBuffer] with the specified [capacity]. - * - * @param capacity The capacity of the buffer. - */ - operator fun invoke(capacity: Int): StashBuffer = StashBufferImpl(capacity) - } -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Time.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Time.kt deleted file mode 100644 index f19f6fe2..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/Time.kt +++ /dev/null @@ -1,75 +0,0 @@ -/* - * 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-core/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt deleted file mode 100644 index c5c54b64..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/TimerScheduler.kt +++ /dev/null @@ -1,92 +0,0 @@ -/* - * 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 { - /** - * 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-core/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt deleted file mode 100644 index eb26add1..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/Behavior.kt +++ /dev/null @@ -1,116 +0,0 @@ -/* - * 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 : DeferredBehavior() { - /** - * 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): Behavior - - // Immediately transfer to implementation - override fun invoke(ctx: ActorContext): Behavior = 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 : ActorContext, 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> -} - -/** - * 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 suspendWithBehavior(block: (Continuation, () -> Behavior) -> Behavior): U = - suspendCoroutine { cont -> - @Suppress("UNCHECKED_CAST") - val ctx = cont.context[SuspendingActorContext] as? SuspendingActorContextImpl - ?: 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 actorContext(): SuspendingActorContext = - suspendCoroutineUninterceptedOrReturn { cont -> - @Suppress("UNCHECKED_CAST") - cont.context[SuspendingActorContext] as? SuspendingActorContext - ?: throw UnsupportedOperationException("Coroutine does not run inside SuspendingBehavior") - } - -/** - * Construct a [Behavior] that uses Kotlin coroutines functionality to handle incoming messages and signals. - */ -fun suspending(block: suspend (SuspendingActorContext) -> Behavior): Behavior { - return object : SuspendingBehavior() { - override suspend fun invoke(ctx: SuspendingActorContext) = block(ctx) - } -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt deleted file mode 100644 index e995c0e3..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Receive.kt +++ /dev/null @@ -1,66 +0,0 @@ -/* - * 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 SuspendingActorContext.receiveOf(): U = - suspendWithBehavior { 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 SuspendingActorContext.ask( - ref: ActorRef, - after: Duration = 0.0, - transform: (ActorRef) -> U -): V { - send(ref, transform(self), after) - return receiveOf() -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt deleted file mode 100644 index 16b6f534..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/coroutines/dsl/Timeout.kt +++ /dev/null @@ -1,42 +0,0 @@ -/* - * 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 { cont, next -> - withTimeout(after) { - cont.resume(Unit) - next() - } -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt deleted file mode 100644 index f1aba25e..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/ActorContext.kt +++ /dev/null @@ -1,43 +0,0 @@ -/* - * 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, signal, after) -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt deleted file mode 100644 index b07cabc0..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Behavior.kt +++ /dev/null @@ -1,48 +0,0 @@ -/* - * 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() { - override fun receive(ctx: ActorContext, msg: Any): Behavior = this - - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior = this - - override fun toString() = "Ignore" -} - -/** - * A [Behavior] object that does not handle any message it receives. - */ -internal object EmptyBehavior : ReceivingBehavior() { - override fun toString() = "Empty" -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt deleted file mode 100644 index 194c2a62..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/BehaviorInterpreter.kt +++ /dev/null @@ -1,201 +0,0 @@ -/* - * 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(initialBehavior: Behavior) { - /** - * The current [Behavior] instance. - */ - var behavior: Behavior = 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, ctx: ActorContext) : this(initialBehavior) { - start(ctx) - } - - /** - * Start the initial behavior. - * - * @param ctx The [ActorContext] to start the behavior in. - */ - fun start(ctx: ActorContext) { - 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) { - 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, next: Behavior) { - 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): Behavior = - 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, 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, 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, msg: Any, isSignal: Boolean): Boolean = - if (isAlive) { - val next = when (val current = behavior) { - is DeferredBehavior -> - throw IllegalStateException("Deferred [$current] should not be passed to interpreter") - is ReceivingBehavior -> - 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): Behavior = - 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 start(ctx: ActorContext, behavior: Behavior): Behavior = - when (behavior) { - is DeferredBehavior -> 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 canonicalize(ctx: ActorContext, current: Behavior, next: Behavior): Behavior = - when (next) { - is SameBehavior, current -> current - is UnhandledBehavior -> current - is DeferredBehavior -> canonicalize(ctx, current, next(ctx)) - else -> next - } - } -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt deleted file mode 100644 index c3346bdf..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/Coroutines.kt +++ /dev/null @@ -1,182 +0,0 @@ -/* - * 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 : SuspendingActorContext { - /** - * The current active behavior - */ - val behavior: Behavior - - /** - * Replace the current active behavior with the specified new behavior. - * - * @param next The behavior to replace the current behavior with. - */ - fun become(next: Behavior) -} - -/** - * 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( - private var actorContext: ActorContext, - initialBehavior: SuspendingBehavior -) : ReceivingBehavior(), SuspendingActorContextImpl { - - /** - * The next behavior to use. - */ - private var next: Behavior = this - - /** - * The [BehaviorInterpreter] to wrap the suspending behavior. - */ - private val interpreter = BehaviorInterpreter(initialBehavior) - - override fun receive(ctx: ActorContext, msg: T): Behavior { - this.actorContext = ctx - return interpreter.also { it.interpretMessage(ctx, msg) }.propagate(next) - } - - override fun receiveSignal(ctx: ActorContext, signal: Signal): Behavior { - this.actorContext = ctx - return interpreter.also { it.interpretSignal(ctx, signal) }.propagate(next) - } - - override val self: ActorRef get() = actorContext.self - - override val time: Instant get() = actorContext.time - - override val children: List> - 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 send(ref: ActorRef, msg: U, after: Duration) = actorContext.send(ref, msg, after) - - override fun spawn(behavior: Behavior, name: String) = actorContext.spawn(behavior, name) - - override fun spawnAnonymous(behavior: Behavior) = 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 { cont, next -> - receiveMessage { msg -> - cont.resume(msg) - next() - } - } - - override suspend fun receiveSignal(): Signal = suspendWithBehavior { cont, next -> - receiveSignal { _, signal -> - cont.resume(signal) - next() - } - } - - override val behavior: Behavior get() = interpreter.behavior - - override fun become(next: Behavior) { - interpreter.become(actorContext, next) - } - - override val key: CoroutineContext.Key<*> = SuspendingActorContext.Key - - /** - * Start the suspending behavior. - */ - internal fun start(): Behavior { - val behavior = interpreter.behavior as SuspendingBehavior - 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> { - override val context = this@SuspendingBehaviorImpl - - override fun resumeWith(result: Result>) { - if (result.isSuccess) { - next = result.getOrNull()!! - } else if (result.isFailure) { - throw result.exceptionOrNull()!! - } - } - } -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt deleted file mode 100644 index 24c3a9d5..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/StashBufferImpl.kt +++ /dev/null @@ -1,74 +0,0 @@ -/* - * 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(private val capacity: Int) : StashBuffer { - /** - * The internal queue used to store the messages. - */ - private val queue = ArrayDeque(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, behavior: Behavior): Behavior { - val messages = queue.toList() - queue.clear() - - val interpreter = BehaviorInterpreter(behavior) - interpreter.start(ctx) - - for (message in messages) { - interpreter.interpretMessage(ctx, message) - } - - return interpreter.behavior - } -} diff --git a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt deleted file mode 100644 index 22bec507..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/TimerSchedulerImpl.kt +++ /dev/null @@ -1,122 +0,0 @@ -/* - * 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(private val ctx: ActorContext) : TimerScheduler { - private val timers = mutableMapOf>() - - 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(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-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt deleted file mode 100644 index bf50b5e8..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationAwareLoggerImpl.kt +++ /dev/null @@ -1,567 +0,0 @@ -/* - * 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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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) { - 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-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt deleted file mode 100644 index 999e30e6..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LocationIgnorantLoggerImpl.kt +++ /dev/null @@ -1,440 +0,0 @@ -/* - * 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-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt b/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt deleted file mode 100644 index f971f08d..00000000 --- a/odcsim/odcsim-core/src/main/kotlin/com/atlarge/odcsim/internal/logging/LoggerImpl.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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) - } - } - } -} diff --git a/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt b/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt deleted file mode 100644 index 023d3efd..00000000 --- a/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/ActorPathTest.kt +++ /dev/null @@ -1,86 +0,0 @@ -/* - * 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.junit.jupiter.api.Assertions.assertEquals -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -/** - * A test suite for the [ActorPath] class. - */ -@DisplayName("ActorPath") -class ActorPathTest { - /** - * Test whether an [ActorPath.Root] may only start with a slash. - */ - @Test - fun `root node may only start with a slash`() { - ActorPath.Root() // Assert slash at start - assertThrows { ActorPath.Root("abc/") } - } - - /** - * Test whether an [ActorPath.Child] disallows names with a slash. - */ - @Test - fun `child node should not allow name with a slash`() { - assertThrows { ActorPath.Child(ActorPath.Root(), "/") } - } - - /** - * Test whether a root node can have a custom name. - */ - @Test - fun `root node can have a custom name`() { - val name = "user" - assertEquals(name, ActorPath.Root(name).name) - } - - /** - * Test whether a child node can be created on a root. - */ - @Test - fun `child node can be created on a root`() { - val root = ActorPath.Root(name = "/user") - val child = root.child("child") - - assertEquals(root, child.parent) - assertEquals("child", child.name) - } - - /** - * Test whether a child node can be created on a child. - */ - @Test - fun `child node can be created on a child`() { - val root = ActorPath.Root(name = "/user").child("child") - val child = root.child("child") - - assertEquals(root, child.parent) - assertEquals("child", child.name) - } -} diff --git a/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt b/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt deleted file mode 100644 index 1eb4f3b9..00000000 --- a/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/BehaviorTest.kt +++ /dev/null @@ -1,77 +0,0 @@ -/* - * 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 com.atlarge.odcsim.internal.BehaviorInterpreter -import com.nhaarman.mockitokotlin2.mock -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test -import org.junit.jupiter.api.assertThrows - -/** - * Test suite for [Behavior] and [BehaviorInterpreter]. - */ -@DisplayName("Behavior") -class BehaviorTest { - /** - * Test whether we cannot start an actor with the [unhandled] behavior. - */ - @Test - fun `should not start with unhandled behavior`() { - val ctx = mock>() - val interpreter = BehaviorInterpreter(unhandled()) - assertThrows { interpreter.start(ctx) } - } - - /** - * Test whether we cannot start an actor with deferred unhandled behavior. - */ - @Test - fun `should not start with deferred unhandled behavior`() { - val ctx = mock>() - val interpreter = BehaviorInterpreter(setup { unhandled() }) - assertThrows { interpreter.start(ctx) } - } - - /** - * Test whether deferred behavior that returns [same] fails. - */ - @Test - fun `should not allow setup to return same`() { - val ctx = mock>() - val interpreter = BehaviorInterpreter(setup { same() }) - assertThrows { interpreter.start(ctx) } - } - - /** - * Test whether deferred behavior that returns [unhandled] fails. - */ - @Test - fun `should not allow setup to return unhandled`() { - val ctx = mock>() - val interpreter = BehaviorInterpreter(setup { unhandled() }) - assertThrows { interpreter.start(ctx) } - } -} diff --git a/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt b/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt deleted file mode 100644 index b59c5ea7..00000000 --- a/odcsim/odcsim-core/src/test/kotlin/com/atlarge/odcsim/CoroutinesTest.kt +++ /dev/null @@ -1,63 +0,0 @@ -/* - * 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.coroutines.SuspendingBehavior -import com.atlarge.odcsim.coroutines.suspending -import com.atlarge.odcsim.internal.BehaviorInterpreter -import com.atlarge.odcsim.internal.EmptyBehavior -import com.nhaarman.mockitokotlin2.mock -import kotlin.coroutines.suspendCoroutine -import org.junit.jupiter.api.Assertions.assertTrue -import org.junit.jupiter.api.DisplayName -import org.junit.jupiter.api.Test - -/** - * Test suite for [SuspendingBehavior] using Kotlin Coroutines. - */ -@DisplayName("Coroutines") -internal class CoroutinesTest { - - @Test - fun `should immediately return new behavior`() { - val ctx = mock>() - val behavior = suspending { empty() } - val interpreter = BehaviorInterpreter(behavior) - interpreter.start(ctx) - assertTrue(interpreter.behavior as Behavior<*> is EmptyBehavior) - } - - @Test - fun `should be able to invoke regular suspend methods`() { - val ctx = mock>() - val behavior = suspending { - suspendCoroutine {} - stopped() - } - val interpreter = BehaviorInterpreter(behavior) - interpreter.start(ctx) - interpreter.interpretMessage(ctx, Unit) - } -} diff --git a/odcsim/odcsim-engine-omega/build.gradle.kts b/odcsim/odcsim-engine-omega/build.gradle.kts index 4540c389..fb08a981 100644 --- a/odcsim/odcsim-engine-omega/build.gradle.kts +++ b/odcsim/odcsim-engine-omega/build.gradle.kts @@ -35,7 +35,7 @@ repositories { } dependencies { - api(project(":odcsim:odcsim-core")) + api(project(":odcsim:odcsim-api")) implementation(kotlin("stdlib")) implementation("org.jetbrains:annotations:17.0.0") diff --git a/odcsim/odcsim-engine-tests/build.gradle.kts b/odcsim/odcsim-engine-tests/build.gradle.kts index 9e4931b7..e68070cc 100644 --- a/odcsim/odcsim-engine-tests/build.gradle.kts +++ b/odcsim/odcsim-engine-tests/build.gradle.kts @@ -35,7 +35,7 @@ repositories { } dependencies { - api(project(":odcsim:odcsim-core")) + api(project(":odcsim:odcsim-api")) implementation(kotlin("stdlib")) implementation("org.junit.jupiter:junit-jupiter-api:${Library.JUNIT_JUPITER}") -- cgit v1.2.3