From 6cea7edf715546f50cf6ff6f85925fa3c4067da3 Mon Sep 17 00:00:00 2001 From: CS Date: Fri, 12 May 2023 16:13:05 +0200 Subject: [PATCH 01/18] ros federates create separate nodes --- org.lflang/src/org/lflang/AttributeUtils.java | 16 ++++++ .../lflang/generator/cpp/CppRos2Generator.kt | 37 ++++++++----- .../generator/cpp/CppRos2NodeGenerator.kt | 20 +++---- .../generator/cpp/CppRos2PackageGenerator.kt | 53 ++++++++++--------- .../org/lflang/validation/AttributeSpec.java | 3 ++ 5 files changed, 81 insertions(+), 48 deletions(-) diff --git a/org.lflang/src/org/lflang/AttributeUtils.java b/org.lflang/src/org/lflang/AttributeUtils.java index ec5edc4b9d..29408e4c81 100644 --- a/org.lflang/src/org/lflang/AttributeUtils.java +++ b/org.lflang/src/org/lflang/AttributeUtils.java @@ -248,4 +248,20 @@ public static boolean isEnclave(Instantiation node) { return getEnclaveAttribute(node) != null; } + /** + * Return the {@code @federate} attribute annotated on the given node. + * + *

Returns null if there is no such attribute. + */ + public static Attribute getFederateAttribute(Instantiation node) { + return findAttributeByName(node, "federate"); + } + + /** + * Return true if the specified instance has an {@code @federate} attribute. TODO: this needs some + * other name bec of c target federate + */ + public static boolean isFederate(Instantiation node) { + return getFederateAttribute(node) != null; + } } diff --git a/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt b/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt index a689a53c88..8ba0de52b4 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt @@ -1,6 +1,8 @@ package org.lflang.generator.cpp +import org.lflang.AttributeUtils import org.lflang.generator.LFGeneratorContext +import org.lflang.reactor import org.lflang.util.FileUtil import java.nio.file.Path @@ -9,24 +11,33 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator override val srcGenPath: Path = generator.fileConfig.srcGenPath.resolve("src") private val packagePath: Path = generator.fileConfig.srcGenPath - private val nodeGenerator = CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig); - private val packageGenerator = CppRos2PackageGenerator(generator, nodeGenerator.nodeName) + private val nodeGenerators : MutableList = mutableListOf(CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig)) + private val packageGenerator = CppRos2PackageGenerator(generator) override fun generatePlatformFiles() { - FileUtil.writeToFile( - nodeGenerator.generateHeader(), - packagePath.resolve("include").resolve("${nodeGenerator.nodeName}.hh"), - true - ) - FileUtil.writeToFile( - nodeGenerator.generateSource(), - packagePath.resolve("src").resolve("${nodeGenerator.nodeName}.cc"), - true - ) + mainReactor.instantiations.forEach{ + if (AttributeUtils.isFederate(it)) { + nodeGenerators.add( + CppRos2NodeGenerator(it.reactor, targetConfig, fileConfig)) + } + } + + for (nodeGen in nodeGenerators) { + FileUtil.writeToFile( + nodeGen.generateHeader(), + packagePath.resolve("include").resolve("${nodeGen.nodeName}.hh"), + true + ) + FileUtil.writeToFile( + nodeGen.generateSource(), + packagePath.resolve("src").resolve("${nodeGen.nodeName}.cc"), + true + ) + } FileUtil.writeToFile(packageGenerator.generatePackageXml(), packagePath.resolve("package.xml"), true) FileUtil.writeToFile( - packageGenerator.generatePackageCmake(generator.cppSources), + packageGenerator.generatePackageCmake(generator.cppSources, nodeGenerators.map { it.nodeName }), packagePath.resolve("CMakeLists.txt"), true ) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt index 22e3c3045a..61e2e2396d 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppRos2NodeGenerator.kt @@ -4,14 +4,14 @@ import org.lflang.TargetConfig import org.lflang.lf.Reactor import org.lflang.toUnixString -/** A C++ code generator for creating a ROS2 node from a main reactor definition */ +/** A C++ code generator for creating a ROS2 node from reactor definition */ class CppRos2NodeGenerator( - private val main: Reactor, + private val reactor: Reactor, private val targetConfig: TargetConfig, private val fileConfig: CppFileConfig ) { - val nodeName = "${fileConfig.name}Node" + val nodeName = "${reactor.name}Node" fun generateHeader(): String { return """ @@ -20,17 +20,17 @@ class CppRos2NodeGenerator( |#include |#include "reactor-cpp/reactor-cpp.hh" | - |#include "${fileConfig.getReactorHeaderPath(main).toUnixString()}" + |#include "${fileConfig.getReactorHeaderPath(reactor).toUnixString()}" | |rclcpp::Node* lf_node{nullptr}; | |class $nodeName : public rclcpp::Node { |private: | std::unique_ptr lf_env; - | std::unique_ptr<${main.name}> lf_main_reactor; + | std::unique_ptr<${reactor.name}> lf_reactor; | - | // main thread of the LF execution - | std::thread lf_main_thread; + | // thread of the LF execution + | std::thread lf_thread; | // an additional thread that we use for waiting for LF termination | // and then shutting down the LF node | std::thread lf_shutdown_thread; @@ -51,7 +51,7 @@ class CppRos2NodeGenerator( |#include | |void $nodeName::wait_for_lf_shutdown() { - | lf_main_thread.join(); + | lf_thread.join(); | this->get_node_options().context()->shutdown("LF execution terminated"); |} | @@ -68,13 +68,13 @@ class CppRos2NodeGenerator( | lf_env = std::make_unique(workers, fast, lf_timeout); | | // instantiate the main reactor - | lf_main_reactor = std::make_unique<${main.name}> ("${main.name}", lf_env.get(), ${main.name}::Parameters{}); + | lf_reactor = std::make_unique<${reactor.name}> ("${reactor.name}", lf_env.get(), ${reactor.name}::Parameters{}); | | // assemble reactor program | lf_env->assemble(); | | // start execution - | lf_main_thread = lf_env->startup(); + | lf_thread = lf_env->startup(); | lf_shutdown_thread = std::thread([this] { wait_for_lf_shutdown(); }); |} | diff --git a/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index b4e00262aa..69ffdbd4d7 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -1,12 +1,13 @@ package org.lflang.generator.cpp import org.lflang.generator.PrependOperator +import org.lflang.joinLn import org.lflang.joinWithLn import org.lflang.toUnixString import java.nio.file.Path /** A C++ code generator for creating the required files for defining a ROS2 package. */ -class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: String) { +class CppRos2PackageGenerator(generator: CppGenerator) { private val fileConfig = generator.fileConfig private val targetConfig = generator.targetConfig val reactorCppSuffix = targetConfig.runtimeVersion ?: "default" @@ -45,7 +46,7 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str """.trimMargin() } - fun generatePackageCmake(sources: List): String { + fun generatePackageCmake(sources: List, nodeNames: List): String { // Resolve path to the cmake include files if any was provided val includeFiles = targetConfig.cmakeIncludes?.map { fileConfig.srcPath.resolve(it).toUnixString() } @@ -68,31 +69,33 @@ class CppRos2PackageGenerator(generator: CppGenerator, private val nodeName: Str |# Invoke find_package() for all build and buildtool dependencies. |find_package(ament_cmake_auto REQUIRED) |ament_auto_find_build_dependencies() + ${ nodeNames.map { + """ + |ament_auto_add_library($it SHARED + | src/$it.cc + ${" | "..sources.joinWithLn { "src/$it" }} + |) + |ament_target_dependencies($it ${dependencies.joinToString(" ")}) + |target_include_directories($it PUBLIC + | "$S{LF_SRC_PKG_PATH}/src" + | "$S{PROJECT_SOURCE_DIR}/src/" + | "$S{PROJECT_SOURCE_DIR}/src/__include__" + |) + |target_link_libraries($it $reactorCppName) + | + |rclcpp_components_register_node($it + | PLUGIN $it + | EXECUTABLE ${it}_exe + |) + |if(MSVC) + | target_compile_options($it PRIVATE /W4) + |else() + | target_compile_options($it PRIVATE -Wall -Wextra -pedantic) + |endif() + """ + }.joinLn()} | - |set(LF_MAIN_TARGET ${fileConfig.name}) | - |ament_auto_add_library($S{LF_MAIN_TARGET} SHARED - | src/$nodeName.cc - ${" | "..sources.joinWithLn { "src/$it" }} - |) - |ament_target_dependencies($S{LF_MAIN_TARGET} ${dependencies.joinToString(" ")}) - |target_include_directories($S{LF_MAIN_TARGET} PUBLIC - | "$S{LF_SRC_PKG_PATH}/src" - | "$S{PROJECT_SOURCE_DIR}/src/" - | "$S{PROJECT_SOURCE_DIR}/src/__include__" - |) - |target_link_libraries($S{LF_MAIN_TARGET} $reactorCppName) - | - |rclcpp_components_register_node($S{LF_MAIN_TARGET} - | PLUGIN "$nodeName" - | EXECUTABLE $S{LF_MAIN_TARGET}_exe - |) - | - |if(MSVC) - | target_compile_options($S{LF_MAIN_TARGET} PRIVATE /W4) - |else() - | target_compile_options($S{LF_MAIN_TARGET} PRIVATE -Wall -Wextra -pedantic) - |endif() | |ament_auto_package() | diff --git a/org.lflang/src/org/lflang/validation/AttributeSpec.java b/org.lflang/src/org/lflang/validation/AttributeSpec.java index c5a530581b..cb15317fa7 100644 --- a/org.lflang/src/org/lflang/validation/AttributeSpec.java +++ b/org.lflang/src/org/lflang/validation/AttributeSpec.java @@ -220,6 +220,9 @@ enum AttrParamType { List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)) )); + ATTRIBUTE_SPECS_BY_NAME.put("federate", + new AttributeSpec(List.of(new AttrParamSpec(EACH_ATTR, AttrParamType.BOOLEAN, true)))); + // attributes that are used internally only by the federated code generation ATTRIBUTE_SPECS_BY_NAME.put("_unordered", new AttributeSpec(null)); ATTRIBUTE_SPECS_BY_NAME.put("_fed_config", new AttributeSpec( From b50e0eb93152fd9fad9a8d9d9005933a03950beb Mon Sep 17 00:00:00 2001 From: CS Date: Mon, 22 May 2023 09:11:24 +0200 Subject: [PATCH 02/18] search for nested federates --- .../org/lflang/generator/cpp/CppRos2Generator.kt | 14 ++++++++++---- 1 file changed, 10 insertions(+), 4 deletions(-) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt b/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt index 8ba0de52b4..101b589d26 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppRos2Generator.kt @@ -2,6 +2,7 @@ package org.lflang.generator.cpp import org.lflang.AttributeUtils import org.lflang.generator.LFGeneratorContext +import org.lflang.lf.Instantiation import org.lflang.reactor import org.lflang.util.FileUtil import java.nio.file.Path @@ -15,11 +16,16 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator private val packageGenerator = CppRos2PackageGenerator(generator) override fun generatePlatformFiles() { - mainReactor.instantiations.forEach{ - if (AttributeUtils.isFederate(it)) { - nodeGenerators.add( - CppRos2NodeGenerator(it.reactor, targetConfig, fileConfig)) + val reactorsToSearch : MutableList = mutableListOf(mainReactor) + while (reactorsToSearch.isNotEmpty()) { + reactorsToSearch[0].instantiations.forEach { + reactorsToSearch.add(it.reactor) + if (AttributeUtils.isFederate(it)) { + nodeGenerators.add( + CppRos2NodeGenerator(it.reactor, targetConfig, fileConfig)) + } } + reactorsToSearch.removeFirst() } for (nodeGen in nodeGenerators) { From 27894747654852302e3cbcdf64d6ab09b5e191ca Mon Sep 17 00:00:00 2001 From: CS Date: Tue, 6 Jun 2023 09:47:42 +0200 Subject: [PATCH 03/18] prep merge from master into this --- .../generator/cpp/ConnectionGenerator.kt | 6 ++ .../generator/cpp/CppConnectionGenerator.kt | 11 ++- .../org/lflang/generator/cpp/CppGenerator.kt | 2 +- .../cpp/CppROS2ConnectionGenerator.kt | 76 +++++++++++++++++++ .../generator/cpp/CppReactorGenerator.kt | 5 +- 5 files changed, 91 insertions(+), 9 deletions(-) create mode 100644 org.lflang/src/org/lflang/generator/cpp/ConnectionGenerator.kt create mode 100644 org.lflang/src/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt diff --git a/org.lflang/src/org/lflang/generator/cpp/ConnectionGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/ConnectionGenerator.kt new file mode 100644 index 0000000000..a9ebb8772e --- /dev/null +++ b/org.lflang/src/org/lflang/generator/cpp/ConnectionGenerator.kt @@ -0,0 +1,6 @@ +package org.lflang.generator.cpp + +interface ConnectionGenerator { + abstract fun generateDeclarations() : String + abstract fun generateInitializers() : String +} \ No newline at end of file diff --git a/org.lflang/src/org/lflang/generator/cpp/CppConnectionGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppConnectionGenerator.kt index c9af656291..7a151f22b4 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppConnectionGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppConnectionGenerator.kt @@ -10,8 +10,7 @@ import org.lflang.lf.Port import org.lflang.lf.Reactor import org.lflang.lf.VarRef -class CppConnectionGenerator(private val reactor: Reactor) { - +class CppConnectionGenerator(private val reactor: Reactor) : ConnectionGenerator { companion object { val Connection.name: String get() = @@ -52,14 +51,14 @@ class CppConnectionGenerator(private val reactor: Reactor) { val Connection.requiresConnectionClass: Boolean get() = isPhysical || delay != null || isEnclaveConnection; } - fun generateDeclarations() = - reactor.connections.mapNotNull { generateDecleration(it) } + override fun generateDeclarations() = + reactor.connections.mapNotNull { generateDeclaration(it) } .joinToString("\n", "// connections \n", postfix = "\n") - fun generateInitializers() = + override fun generateInitializers() = reactor.connections.mapNotNull { generateConstructorInitializer(it) }.joinLn() - private fun generateDecleration(connection: Connection): String? = + private fun generateDeclaration(connection: Connection): String? = with(connection) { if (requiresConnectionClass) { if (hasMultipleConnections) { diff --git a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt index e739560ba8..887861a918 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppGenerator.kt @@ -149,7 +149,7 @@ class CppGenerator( // generate header and source files for all reactors for (r in reactors) { - val generator = CppReactorGenerator(r, fileConfig, errorReporter) + val generator = CppReactorGenerator(r, fileConfig, errorReporter, targetConfig) val headerFile = fileConfig.getReactorHeaderPath(r) val sourceFile = if (r.isGeneric) fileConfig.getReactorHeaderImplPath(r) else fileConfig.getReactorSourcePath(r) val reactorCodeMap = CodeMap.fromGeneratedCode(generator.generateSource()) diff --git a/org.lflang/src/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt new file mode 100644 index 0000000000..937167c4e6 --- /dev/null +++ b/org.lflang/src/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt @@ -0,0 +1,76 @@ +package org.lflang.generator.cpp + +import org.lflang.* +import org.lflang.generator.cpp.CppConnectionGenerator.Companion.cppType +import org.lflang.generator.cpp.CppConnectionGenerator.Companion.isEnclaveConnection +import org.lflang.generator.cpp.CppInstanceGenerator.Companion.isEnclave +import org.lflang.generator.cpp.CppPortGenerator.Companion.dataType +import org.lflang.lf.Connection +import org.lflang.lf.Port +import org.lflang.lf.Reactor +import org.lflang.lf.VarRef + +class CppROS2ConnectionGenerator(private val reactor: Reactor) : ConnectionGenerator { + companion object { + val Connection.name: String + get() = + "connection_" + leftPorts.joinToString("__") { + if (it.container == null) { + it.variable.name + } else { + it.container.name + "_" + it.variable.name + } + } + + val Connection.cppType: String + get() { + val leftPort = leftPorts.first() + return when { + isSubscriberConnection -> "reactor::ROS2SubscriberConnection" //<${leftPort.dataType}>" + else -> throw IllegalArgumentException("Unsupported connection type") + } + } + + val Connection.isSubscriberConnection: Boolean + get() { + return true + for (port in leftPorts + rightPorts) { + if (port.container?.isEnclave == true) { + return true + } + } + return false + } + + } + + override fun generateDeclarations() = + reactor.connections.mapNotNull { generateDeclaration(it) } + .joinToString("\n", "// connections \n", postfix = "\n") + + override fun generateInitializers() = + reactor.connections.mapNotNull { generateConstructorInitializer(it) }.joinLn() + + private fun generateDeclaration(connection: Connection): String? = + with(connection) { + return null + if (hasMultipleConnections) { + "std::vector> ${connection.name};" + } else { + "${connection.cppType} ${connection.name};" + } + } + + private fun generateConstructorInitializer(connection: Connection): String? = with(connection) { + return null + if (!hasMultipleConnections) { + when { + //isSubscriberConnection && delay == null -> """, $name{"$name", ${leftPorts[0].container.name}->__lf_env.get()}""" + //isSubscriberConnection && delay != null -> """, $name{"$name", ${leftPorts[0].container.name}->__lf_env.get(), ${delay.toCppTime()}}""" + //!isSubscriberConnection && delay == null -> """, $name{"$name", ${rightPorts[0].container.name}->__lf_env.get()}""" + else -> """, $name{"$name", this, ${delay.toCppTime()}}""" + } + } else null + } + +} diff --git a/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt b/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt index 78df4519f2..b110d6476a 100644 --- a/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt +++ b/org.lflang/src/org/lflang/generator/cpp/CppReactorGenerator.kt @@ -24,6 +24,7 @@ package org.lflang.generator.cpp import org.lflang.ErrorReporter +import org.lflang.TargetConfig import org.lflang.generator.PrependOperator import org.lflang.isGeneric import org.lflang.lf.Reactor @@ -33,7 +34,7 @@ import org.lflang.toUnixString /** * A C++ code generator that produces a C++ class representing a single reactor */ -class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfig, errorReporter: ErrorReporter) { +class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfig, errorReporter: ErrorReporter, targetConfig: TargetConfig) { /** Comment to be inserted at the top of generated files */ private val fileComment = fileComment(reactor.eResource()) @@ -56,7 +57,7 @@ class CppReactorGenerator(private val reactor: Reactor, fileConfig: CppFileConfi private val ports = CppPortGenerator(reactor) private val reactions = CppReactionGenerator(reactor, ports, instances) private val assemble = CppAssembleMethodGenerator(reactor) - private val connections = CppConnectionGenerator(reactor) + private val connections : ConnectionGenerator = if (targetConfig.ros2) CppROS2ConnectionGenerator(reactor) else CppConnectionGenerator(reactor) private fun publicPreamble() = reactor.preambles.filter { it.isPublic } From f3a9b00686e5b4c4bbb5437f917c8f31e9e63ff6 Mon Sep 17 00:00:00 2001 From: CS Date: Mon, 19 Jun 2023 12:22:04 +0200 Subject: [PATCH 04/18] endpoint generation --- .../org/lflang/generator/cpp/CppRos2Generator.kt | 1 + .../lflang/generator/cpp/CppRos2NodeGenerator.kt | 13 +++++++++++-- .../lflang/generator/cpp/CppRos2PackageGenerator.kt | 2 +- 3 files changed, 13 insertions(+), 3 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt index 101b589d26..8c15293422 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt @@ -17,6 +17,7 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator override fun generatePlatformFiles() { val reactorsToSearch : MutableList = mutableListOf(mainReactor) + /** Recursively searching for federates */ while (reactorsToSearch.isNotEmpty()) { reactorsToSearch[0].instantiations.forEach { reactorsToSearch.add(it.reactor) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt index 61e2e2396d..5cb01fe007 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt @@ -2,7 +2,11 @@ package org.lflang.generator.cpp import org.lflang.TargetConfig import org.lflang.lf.Reactor +import org.lflang.toText import org.lflang.toUnixString +import org.lflang.generator.cpp.CppPortGenerator.Companion.dataType +import org.lflang.inferredType +import org.lflang.lf.VarRef /** A C++ code generator for creating a ROS2 node from reactor definition */ class CppRos2NodeGenerator( @@ -28,7 +32,8 @@ class CppRos2NodeGenerator( |private: | std::unique_ptr lf_env; | std::unique_ptr<${reactor.name}> lf_reactor; - | + | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n"){ "std::unique_ptr> ${it.name}_sub;" } } + | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n"){ "std::unique_ptr> ${it.name}_pub;" } } | // thread of the LF execution | std::thread lf_thread; | // an additional thread that we use for waiting for LF termination @@ -69,7 +74,11 @@ class CppRos2NodeGenerator( | | // instantiate the main reactor | lf_reactor = std::make_unique<${reactor.name}> ("${reactor.name}", lf_env.get(), ${reactor.name}::Parameters{}); - | + | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n") + { "${it.name}_sub = std::make_unique>(\"${it.name}_sub\", lf_env.get(), false, std::chrono::nanoseconds(0));" + "${it.name}_sub->add_port(&lf_reactor->${it.name});" + }} + | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n") { "${it.name}_pub = std::make_unique>();" + + "${it.name}_pub->set_port(&lf_reactor->${it.name});" }} | // assemble reactor program | lf_env->assemble(); | diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index 69ffdbd4d7..ef80cf820f 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -33,7 +33,7 @@ class CppRos2PackageGenerator(generator: CppGenerator) { | ament_cmake_auto | ${" |"..dependencies.joinWithLn { "$it" }} - | + | | ament_lint_auto | ament_lint_common | From 94475aa755101839e2afb53702e5462cdaa8cabe Mon Sep 17 00:00:00 2001 From: CS Date: Mon, 26 Jun 2023 12:06:49 +0200 Subject: [PATCH 05/18] swapped build to executables --- .../lflang/generator/cpp/CppRos2NodeGenerator.kt | 15 +++++++++++---- .../generator/cpp/CppRos2PackageGenerator.kt | 12 +++++++----- 2 files changed, 18 insertions(+), 9 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt index 5cb01fe007..59540b0f7b 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt @@ -60,7 +60,7 @@ class CppRos2NodeGenerator( | this->get_node_options().context()->shutdown("LF execution terminated"); |} | - |$nodeName::$nodeName(const rclcpp::NodeOptions& node_options) + |$nodeName::$nodeName(const rclcpp::NodeOptions& node_options = rclcpp::NodeOptions()) | : Node("$nodeName", node_options) { | unsigned workers = ${if (targetConfig.workers != 0) targetConfig.workers else "std::thread::hardware_concurrency()"}; | bool fast{${targetConfig.fastMode}}; @@ -74,10 +74,11 @@ class CppRos2NodeGenerator( | | // instantiate the main reactor | lf_reactor = std::make_unique<${reactor.name}> ("${reactor.name}", lf_env.get(), ${reactor.name}::Parameters{}); + | ${reactor} | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n") - { "${it.name}_sub = std::make_unique>(\"${it.name}_sub\", lf_env.get(), false, std::chrono::nanoseconds(0));" + "${it.name}_sub->add_port(&lf_reactor->${it.name});" + { "${it.name}_sub = std::make_unique>(\"test\",\"${it.name}_sub\", lf_env.get(), false, std::chrono::nanoseconds(0));" + "${it.name}_sub->add_port(&lf_reactor->${it.name});" }} - | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n") { "${it.name}_pub = std::make_unique>();" + + | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n") { "${it.name}_pub = std::make_unique>(\"test\");" + "${it.name}_pub->set_port(&lf_reactor->${it.name});" }} | // assemble reactor program | lf_env->assemble(); @@ -94,7 +95,13 @@ class CppRos2NodeGenerator( | lf_shutdown_thread.join(); |} | - |RCLCPP_COMPONENTS_REGISTER_NODE($nodeName) + |int main(int argc, char **argv) { + | rclcpp::init(argc, argv); + | auto node = std::make_shared<$nodeName>(); + | rclcpp::spin(node); + | rclcpp::shutdown(); + |} + | """.trimMargin() } } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index ef80cf820f..358b3fa338 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -71,22 +71,24 @@ class CppRos2PackageGenerator(generator: CppGenerator) { |ament_auto_find_build_dependencies() ${ nodeNames.map { """ - |ament_auto_add_library($it SHARED + |add_executable($it | src/$it.cc ${" | "..sources.joinWithLn { "src/$it" }} |) |ament_target_dependencies($it ${dependencies.joinToString(" ")}) |target_include_directories($it PUBLIC | "$S{LF_SRC_PKG_PATH}/src" + | "$S{PROJECT_SOURCE_DIR}/include/" | "$S{PROJECT_SOURCE_DIR}/src/" | "$S{PROJECT_SOURCE_DIR}/src/__include__" |) |target_link_libraries($it $reactorCppName) | - |rclcpp_components_register_node($it - | PLUGIN $it - | EXECUTABLE ${it}_exe - |) + |install( + | TARGETS $it + | DESTINATION lib/$S{PROJECT_NAME} + | ) + | |if(MSVC) | target_compile_options($it PRIVATE /W4) |else() From 046515d417eab940cbca214bc4ca2d436981d3f1 Mon Sep 17 00:00:00 2001 From: CS Date: Sun, 2 Jul 2023 12:15:13 +0200 Subject: [PATCH 06/18] add simple tests --- test/Cpp/src/federated/StringConnection.lf | 56 ++++++++++++++++++++++ test/Cpp/src/federated/VoidConnection.lf | 55 +++++++++++++++++++++ 2 files changed, 111 insertions(+) create mode 100644 test/Cpp/src/federated/StringConnection.lf create mode 100644 test/Cpp/src/federated/VoidConnection.lf diff --git a/test/Cpp/src/federated/StringConnection.lf b/test/Cpp/src/federated/StringConnection.lf new file mode 100644 index 0000000000..9f26ec39bf --- /dev/null +++ b/test/Cpp/src/federated/StringConnection.lf @@ -0,0 +1,56 @@ +target Cpp { + ros2: true, + keepalive: true, + ros2-dependencies: ["std_msgs"], +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/string.hpp" +=} + +reactor Pub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + + timer t(0, 500 ms) + + output out : std_msgs::msg::String + + reaction(startup) {= + =} + + reaction(t) -> out {= + std_msgs::msg::String msg; + msg.data = "Hello"; + out.set(msg); + =} +} + +reactor Sub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + state count: unsigned(0) + + input in : std_msgs::msg::String + + reaction(startup) {= + =} + + reaction(in) {= + reactor::log::Info() << "I heard " << in.get()->data; + =} +} + +main reactor { + @federate + pub = new Pub() + @federate + sub = new Sub() + + pub.out -> sub.in +} \ No newline at end of file diff --git a/test/Cpp/src/federated/VoidConnection.lf b/test/Cpp/src/federated/VoidConnection.lf new file mode 100644 index 0000000000..557fac23a0 --- /dev/null +++ b/test/Cpp/src/federated/VoidConnection.lf @@ -0,0 +1,55 @@ +target Cpp { + ros2: true, + keepalive: true, + ros2-dependencies: ["std_msgs"], +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/string.hpp" +=} + +reactor Pub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + + + timer t(0, 500 ms) + + output out : void + + reaction(startup) {= + =} + + reaction(t) -> out {= + out.set(); + =} +} + +reactor Sub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + state count: unsigned(0) + + input in : void + + reaction(startup) {= + =} + + reaction(in) {= + reactor::log::Info() << "I heard"; + =} +} + +main reactor { + @federate + pub = new Pub() + @federate + sub = new Sub() + + pub.out -> sub.in +} \ No newline at end of file From 7dfd181d20305bed6bb824e6c986a0fb185abf6f Mon Sep 17 00:00:00 2001 From: CS Date: Fri, 4 Aug 2023 00:22:10 +0200 Subject: [PATCH 07/18] physical, logical connections, wrapping port types --- .../lflang/generator/cpp/CppRos2Generator.kt | 27 +++++++- .../cpp/CppRos2MessageWrapperGenerator.kt | 65 +++++++++++++++++++ .../generator/cpp/CppRos2NodeGenerator.kt | 47 ++++++++++---- .../generator/cpp/CppRos2PackageGenerator.kt | 56 ++++++++++------ .../lib/cpp/lf_msgs_ros/CMakeLists.txt | 12 ++++ .../resources/lib/cpp/lf_msgs_ros/msg/Tag.msg | 2 + .../resources/lib/cpp/lf_msgs_ros/package.xml | 22 +++++++ .../{VoidConnection.lf => EmptyConnection.lf} | 17 +++-- test/Cpp/src/federated/StringConnection.lf | 4 +- 9 files changed, 209 insertions(+), 43 deletions(-) create mode 100644 core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt create mode 100644 core/src/main/resources/lib/cpp/lf_msgs_ros/CMakeLists.txt create mode 100644 core/src/main/resources/lib/cpp/lf_msgs_ros/msg/Tag.msg create mode 100644 core/src/main/resources/lib/cpp/lf_msgs_ros/package.xml rename test/Cpp/src/federated/{VoidConnection.lf => EmptyConnection.lf} (67%) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt index 8c15293422..3d25915bd9 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt @@ -2,7 +2,6 @@ package org.lflang.generator.cpp import org.lflang.AttributeUtils import org.lflang.generator.LFGeneratorContext -import org.lflang.lf.Instantiation import org.lflang.reactor import org.lflang.util.FileUtil import java.nio.file.Path @@ -14,6 +13,7 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator private val packagePath: Path = generator.fileConfig.srcGenPath private val nodeGenerators : MutableList = mutableListOf(CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig)) private val packageGenerator = CppRos2PackageGenerator(generator) + private val lfMsgsRosPackageName = "lf_msgs_ros" override fun generatePlatformFiles() { val reactorsToSearch : MutableList = mutableListOf(mainReactor) @@ -29,6 +29,23 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator reactorsToSearch.removeFirst() } + packageGenerator.nodeGenerators = nodeGenerators + + // tag message package + val lfMsgsRosDir = "/lib/cpp/$lfMsgsRosPackageName" + FileUtil.copyFromClassPath(lfMsgsRosDir, fileConfig.srcGenBasePath, true, false) + + val messageTypesToWrap : MutableSet = mutableSetOf() + for (nodeGen in nodeGenerators) { + messageTypesToWrap.addAll(nodeGen.getMessageTypes()) + } + val msgWrapGen = CppRos2MessageWrapperGenerator(messageTypesToWrap) + for ((messageFileName, messageFileContent) in msgWrapGen.generateMessageFiles()) { + FileUtil.writeToFile(messageFileContent, fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("msg").resolve("$messageFileName.msg")) + } + FileUtil.writeToFile(msgWrapGen.generatePackageCmake(), fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("CMakeLists.txt")) + FileUtil.writeToFile(msgWrapGen.generatePackageXml(), fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("package.xml")) + for (nodeGen in nodeGenerators) { FileUtil.writeToFile( nodeGen.generateHeader(), @@ -44,7 +61,7 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator FileUtil.writeToFile(packageGenerator.generatePackageXml(), packagePath.resolve("package.xml"), true) FileUtil.writeToFile( - packageGenerator.generatePackageCmake(generator.cppSources, nodeGenerators.map { it.nodeName }), + packageGenerator.generatePackageCmake(generator.cppSources), packagePath.resolve("CMakeLists.txt"), true ) @@ -68,14 +85,18 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator "colcon", listOf( "build", "--packages-select", + lfMsgsRosPackageName, + "lf_wrapped_msgs", fileConfig.name, packageGenerator.reactorCppName, "--cmake-args", "-DLF_REACTOR_CPP_SUFFIX=${packageGenerator.reactorCppSuffix}", "-DLF_SRC_PKG_PATH=${fileConfig.srcPkgPath}" ), - fileConfig.outPath + fileConfig.srcGenBasePath ) + + val returnCode = colconCommand?.run(context.cancelIndicator); if (returnCode != 0 && !errorReporter.errorsOccurred) { // If errors occurred but none were reported, then the following message is the best we can do. diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt new file mode 100644 index 0000000000..854ec91c08 --- /dev/null +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt @@ -0,0 +1,65 @@ +package org.lflang.generator.cpp + +import org.lflang.capitalize +import org.lflang.joinWithLn + +class CppRos2MessageWrapperGenerator (private val messageTypesToWrap : Set){ + + fun generateMessageFiles() : List> { + return messageTypesToWrap.map{ + Pair( + // std_msgs have an extra "_" which needs to be removed, ROS message names must follow this regex: '^[A-Z][A-Za-z0-9]*$' + it.replace("::", "").replace("_", "").capitalize() + "Wrapped", + """ + |lf_msgs_ros/Tag tag + |${it.replace("::", "/").replace("msg/", "") + " message"} + """.trimMargin() + ) + } + + } + fun generatePackageCmake(): String { + val S = '$' + return """ + |cmake_minimum_required(VERSION 3.5) + |project(lf_wrapped_msgs) + + |find_package(lf_msgs_ros REQUIRED) + |find_package(std_msgs REQUIRED) + | + |rosidl_generate_interfaces($S{PROJECT_NAME} + |${messageTypesToWrap.joinWithLn{"msg/${it.replace("::", "").replace("_", "").capitalize()}Wrapped.msg"}} + |DEPENDENCIES std_msgs lf_msgs_ros + |) + | + |ament_export_dependencies(rosidl_default_runtime) + |ament_package() + """.trimMargin() + + } + + fun generatePackageXml(): String { + return """ + | + | + | + | lf_wrapped_msgs + | 0.0.0 + | Generated message wrappers including original message type and a LF-tag + | Todo + | Todo + | + | rosidl_default_generators + | std_msgs + | lf_msgs_ros + | rosidl_default_runtime + | + | rosidl_interface_packages + | + | ament_cmake + | + | + | + """.trimMargin() + } +} \ No newline at end of file diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt index 59540b0f7b..c6938f86d0 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt @@ -1,28 +1,35 @@ package org.lflang.generator.cpp -import org.lflang.TargetConfig +import org.lflang.* +import org.lflang.lf.Connection import org.lflang.lf.Reactor -import org.lflang.toText -import org.lflang.toUnixString -import org.lflang.generator.cpp.CppPortGenerator.Companion.dataType -import org.lflang.inferredType import org.lflang.lf.VarRef /** A C++ code generator for creating a ROS2 node from reactor definition */ class CppRos2NodeGenerator( - private val reactor: Reactor, + public val reactor: Reactor, private val targetConfig: TargetConfig, private val fileConfig: CppFileConfig ) { val nodeName = "${reactor.name}Node" + fun getMessageTypes() : Set { + val s = reactor.inputs.map{it.inferredType.cppType}.toMutableSet() + s.addAll(reactor.outputs.map{it.inferredType.cppType}) + return s + } + fun generateHeader(): String { return """ |#pragma once | |#include + |#include "${reactor.name}.hh" |#include "reactor-cpp/reactor-cpp.hh" + |${getMessageTypes().map { msgType -> + ("#include \"lf_wrapped_msgs/msg/"+ msgType.replace("::", "").replace("_", "").replaceFirstChar(Char::lowercase)+ "Wrapped.hpp\"").map { if (it.isUpperCase()) "_${it.lowercase()}" else it}.joinToString("") + }.joinLn() } | |#include "${fileConfig.getReactorHeaderPath(reactor).toUnixString()}" | @@ -32,8 +39,10 @@ class CppRos2NodeGenerator( |private: | std::unique_ptr lf_env; | std::unique_ptr<${reactor.name}> lf_reactor; - | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n"){ "std::unique_ptr> ${it.name}_sub;" } } - | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n"){ "std::unique_ptr> ${it.name}_pub;" } } + | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n"){ + "std::unique_ptr> ${it.name}_sub;" } } + | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n"){ + "std::unique_ptr> ${it.name}_pub;" } } | // thread of the LF execution | std::thread lf_thread; | // an additional thread that we use for waiting for LF termination @@ -51,6 +60,7 @@ class CppRos2NodeGenerator( fun generateSource(): String { return """ |#include "$nodeName.hh" + |#include "${reactor.name}.hh" |#include | |#include @@ -74,12 +84,23 @@ class CppRos2NodeGenerator( | | // instantiate the main reactor | lf_reactor = std::make_unique<${reactor.name}> ("${reactor.name}", lf_env.get(), ${reactor.name}::Parameters{}); - | ${reactor} | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n") - { "${it.name}_sub = std::make_unique>(\"test\",\"${it.name}_sub\", lf_env.get(), false, std::chrono::nanoseconds(0));" + "${it.name}_sub->add_port(&lf_reactor->${it.name});" - }} - | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n") { "${it.name}_pub = std::make_unique>(\"test\");" + - "${it.name}_pub->set_port(&lf_reactor->${it.name});" }} + { + val outputConnectedToThisInput : String + println("Connections here") + println(reactor.connections) + println(reactor.name) + val conAndInd : Pair? = reactor.allConnections.map{ con -> Pair(con, con.rightPorts.indexOf(it as VarRef)) }.find { (_, index) -> index >= 0} + outputConnectedToThisInput = if (conAndInd == null) "test" + else conAndInd.first.leftPorts[conAndInd.second].container.name + conAndInd.first.leftPorts[conAndInd.second].name + return@joinToString "${it.name}_sub = std::make_unique>(" + + "\"$outputConnectedToThisInput\",\"${it.name}_sub\", lf_env.get(), false, std::chrono::nanoseconds(0));" + "${it.name}_sub->add_port(&lf_reactor->${it.name});" + } + } + | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n") { + "${it.name}_pub = std::make_unique>(\"test\");" + + "${it.name}_pub->set_port(&lf_reactor->${it.name});" } + } | // assemble reactor program | lf_env->assemble(); | diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index 358b3fa338..551072f8bf 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -18,6 +18,8 @@ class CppRos2PackageGenerator(generator: CppGenerator) { @Suppress("PrivatePropertyName") // allows us to use capital S as variable name below private val S = '$' // a little trick to escape the dollar sign with $S + var nodeGenerators = emptyList() + fun generatePackageXml(): String { return """ | @@ -28,12 +30,13 @@ class CppRos2PackageGenerator(generator: CppGenerator) { | Autogenerated from ${fileConfig.srcFile} | Todo | Todo - | + | | ament_cmake | ament_cmake_auto | ${" |"..dependencies.joinWithLn { "$it" }} - | + | lf_msgs_ros + | lf_wrapped_msgs | ament_lint_auto | ament_lint_common | @@ -46,7 +49,7 @@ class CppRos2PackageGenerator(generator: CppGenerator) { """.trimMargin() } - fun generatePackageCmake(sources: List, nodeNames: List): String { + fun generatePackageCmake(sources: List): String { // Resolve path to the cmake include files if any was provided val includeFiles = targetConfig.cmakeIncludes?.map { fileConfig.srcPath.resolve(it).toUnixString() } @@ -68,38 +71,45 @@ class CppRos2PackageGenerator(generator: CppGenerator) { | |# Invoke find_package() for all build and buildtool dependencies. |find_package(ament_cmake_auto REQUIRED) + |find_package(lf_msgs_ros REQUIRED) + |find_package(lf_wrapped_msgs REQUIRED) |ament_auto_find_build_dependencies() - ${ nodeNames.map { + |include_directories( + | include + | src/${fileConfig.srcGenBasePath.relativize(fileConfig.srcGenPkgPath).toUnixString()} + | "$S{LF_SRC_PKG_PATH}/src" + | "$S{PROJECT_SOURCE_DIR}/include/" + | "$S{PROJECT_SOURCE_DIR}/src/" + | "$S{PROJECT_SOURCE_DIR}/src/__include__" + | ) + ${ nodeGenerators.map { """ - |add_executable($it - | src/$it.cc - ${" | "..sources.joinWithLn { "src/$it" }} - |) - |ament_target_dependencies($it ${dependencies.joinToString(" ")}) - |target_include_directories($it PUBLIC - | "$S{LF_SRC_PKG_PATH}/src" - | "$S{PROJECT_SOURCE_DIR}/include/" - | "$S{PROJECT_SOURCE_DIR}/src/" - | "$S{PROJECT_SOURCE_DIR}/src/__include__" + |add_executable(${it.nodeName} + ${ nodeGenerators.map { it1 -> + """| src/${fileConfig.srcGenBasePath.relativize(fileConfig.srcGenPkgPath).toUnixString()}/${it1.reactor.name}.cc""" + }.joinLn() } + | src/${it.nodeName}.cc + | |) - |target_link_libraries($it $reactorCppName) + |ament_target_dependencies(${it.nodeName} ${dependencies.joinToString(" ")} lf_msgs_ros lf_wrapped_msgs) + |target_link_libraries(${it.nodeName} $reactorCppName) | |install( - | TARGETS $it + | TARGETS ${it.nodeName} | DESTINATION lib/$S{PROJECT_NAME} | ) | |if(MSVC) - | target_compile_options($it PRIVATE /W4) + | target_compile_options(${it.nodeName} PRIVATE /W4) |else() - | target_compile_options($it PRIVATE -Wall -Wextra -pedantic) + | target_compile_options(${it.nodeName} PRIVATE -Wall -Wextra -pedantic) |endif() """ }.joinLn()} | | | - |ament_auto_package() + |ament_package() | ${" |"..(includeFiles?.joinWithLn { "include(\"$it\")" } ?: "")} """.trimMargin() @@ -113,8 +123,12 @@ class CppRos2PackageGenerator(generator: CppGenerator) { return """ |#!/bin/bash |script_dir="$S(dirname -- "$S(readlink -f -- "${S}0")")" - |source "$S{script_dir}/$relPath/install/setup.sh" - |ros2 run ${fileConfig.name} ${fileConfig.name}_exe + |source "$S{script_dir}/$relPath/src-gen/install/setup.sh" + |${ nodeGenerators.map { + "ros2 run ${fileConfig.name} ${it.nodeName} &" + }.joinLn() } + |trap "killall" EXIT SIGINT SIGTERM + |wait """.trimMargin() } } diff --git a/core/src/main/resources/lib/cpp/lf_msgs_ros/CMakeLists.txt b/core/src/main/resources/lib/cpp/lf_msgs_ros/CMakeLists.txt new file mode 100644 index 0000000000..4159095e44 --- /dev/null +++ b/core/src/main/resources/lib/cpp/lf_msgs_ros/CMakeLists.txt @@ -0,0 +1,12 @@ +cmake_minimum_required(VERSION 3.5) +project(lf_msgs_ros) + +find_package(rosidl_default_generators REQUIRED) + +rosidl_generate_interfaces(${PROJECT_NAME} + "msg/Tag.msg" + ) + +ament_package() + + diff --git a/core/src/main/resources/lib/cpp/lf_msgs_ros/msg/Tag.msg b/core/src/main/resources/lib/cpp/lf_msgs_ros/msg/Tag.msg new file mode 100644 index 0000000000..50e3f12146 --- /dev/null +++ b/core/src/main/resources/lib/cpp/lf_msgs_ros/msg/Tag.msg @@ -0,0 +1,2 @@ +int64 time_point +uint64 microstep \ No newline at end of file diff --git a/core/src/main/resources/lib/cpp/lf_msgs_ros/package.xml b/core/src/main/resources/lib/cpp/lf_msgs_ros/package.xml new file mode 100644 index 0000000000..dfe2bb15ad --- /dev/null +++ b/core/src/main/resources/lib/cpp/lf_msgs_ros/package.xml @@ -0,0 +1,22 @@ + + + + + lf_msgs_ros + 0.0.0 + ROS messages for lingua franca + user + TODO: License declaration + ament_cmake + ament_lint_auto + ament_lint_common + rosidl_default_generators + rosidl_default_runtime + rosidl_interface_packages + + ament_cmake + + + diff --git a/test/Cpp/src/federated/VoidConnection.lf b/test/Cpp/src/federated/EmptyConnection.lf similarity index 67% rename from test/Cpp/src/federated/VoidConnection.lf rename to test/Cpp/src/federated/EmptyConnection.lf index 557fac23a0..3513eb33e6 100644 --- a/test/Cpp/src/federated/VoidConnection.lf +++ b/test/Cpp/src/federated/EmptyConnection.lf @@ -1,12 +1,12 @@ target Cpp { ros2: true, - keepalive: true, + timeout: 5 s, ros2-dependencies: ["std_msgs"], } public preamble {= #include "rclcpp/rclcpp.hpp" - #include "std_msgs/msg/string.hpp" + #include "std_msgs/msg/empty.hpp" =} reactor Pub { @@ -18,13 +18,15 @@ reactor Pub { timer t(0, 500 ms) - output out : void + output out : std_msgs::msg::Empty reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Hi"); =} reaction(t) -> out {= - out.set(); + std_msgs::msg::Empty m; + out.set(m); =} } @@ -35,9 +37,10 @@ reactor Sub { =} state count: unsigned(0) - input in : void + input in : std_msgs::msg::Empty reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Hi"); =} reaction(in) {= @@ -51,5 +54,9 @@ main reactor { @federate sub = new Sub() + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Hi"); + =} + pub.out -> sub.in } \ No newline at end of file diff --git a/test/Cpp/src/federated/StringConnection.lf b/test/Cpp/src/federated/StringConnection.lf index 9f26ec39bf..2fa4b487cc 100644 --- a/test/Cpp/src/federated/StringConnection.lf +++ b/test/Cpp/src/federated/StringConnection.lf @@ -1,6 +1,6 @@ target Cpp { ros2: true, - keepalive: true, + timeout: 5s, ros2-dependencies: ["std_msgs"], } @@ -20,6 +20,7 @@ reactor Pub { output out : std_msgs::msg::String reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Pub here"); =} reaction(t) -> out {= @@ -39,6 +40,7 @@ reactor Sub { input in : std_msgs::msg::String reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Sub here"); =} reaction(in) {= From 4a45bf7ccd99e264b82104c7e6766a966886abde Mon Sep 17 00:00:00 2001 From: CS Date: Mon, 7 Aug 2023 02:55:51 +0200 Subject: [PATCH 08/18] cleanup message type string operations --- .../cpp/CppROS2ConnectionGenerator.kt | 76 ------------------- .../lflang/generator/cpp/CppRos2Extensions.kt | 46 +++++++++++ .../lflang/generator/cpp/CppRos2Generator.kt | 9 ++- .../cpp/CppRos2MessageWrapperGenerator.kt | 25 ++++-- .../generator/cpp/CppRos2NodeGenerator.kt | 17 ++--- 5 files changed, 73 insertions(+), 100 deletions(-) delete mode 100644 core/src/main/kotlin/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt create mode 100644 core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt deleted file mode 100644 index 937167c4e6..0000000000 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppROS2ConnectionGenerator.kt +++ /dev/null @@ -1,76 +0,0 @@ -package org.lflang.generator.cpp - -import org.lflang.* -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.cppType -import org.lflang.generator.cpp.CppConnectionGenerator.Companion.isEnclaveConnection -import org.lflang.generator.cpp.CppInstanceGenerator.Companion.isEnclave -import org.lflang.generator.cpp.CppPortGenerator.Companion.dataType -import org.lflang.lf.Connection -import org.lflang.lf.Port -import org.lflang.lf.Reactor -import org.lflang.lf.VarRef - -class CppROS2ConnectionGenerator(private val reactor: Reactor) : ConnectionGenerator { - companion object { - val Connection.name: String - get() = - "connection_" + leftPorts.joinToString("__") { - if (it.container == null) { - it.variable.name - } else { - it.container.name + "_" + it.variable.name - } - } - - val Connection.cppType: String - get() { - val leftPort = leftPorts.first() - return when { - isSubscriberConnection -> "reactor::ROS2SubscriberConnection" //<${leftPort.dataType}>" - else -> throw IllegalArgumentException("Unsupported connection type") - } - } - - val Connection.isSubscriberConnection: Boolean - get() { - return true - for (port in leftPorts + rightPorts) { - if (port.container?.isEnclave == true) { - return true - } - } - return false - } - - } - - override fun generateDeclarations() = - reactor.connections.mapNotNull { generateDeclaration(it) } - .joinToString("\n", "// connections \n", postfix = "\n") - - override fun generateInitializers() = - reactor.connections.mapNotNull { generateConstructorInitializer(it) }.joinLn() - - private fun generateDeclaration(connection: Connection): String? = - with(connection) { - return null - if (hasMultipleConnections) { - "std::vector> ${connection.name};" - } else { - "${connection.cppType} ${connection.name};" - } - } - - private fun generateConstructorInitializer(connection: Connection): String? = with(connection) { - return null - if (!hasMultipleConnections) { - when { - //isSubscriberConnection && delay == null -> """, $name{"$name", ${leftPorts[0].container.name}->__lf_env.get()}""" - //isSubscriberConnection && delay != null -> """, $name{"$name", ${leftPorts[0].container.name}->__lf_env.get(), ${delay.toCppTime()}}""" - //!isSubscriberConnection && delay == null -> """, $name{"$name", ${rightPorts[0].container.name}->__lf_env.get()}""" - else -> """, $name{"$name", this, ${delay.toCppTime()}}""" - } - } else null - } - -} diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt new file mode 100644 index 0000000000..ccef9afbfa --- /dev/null +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt @@ -0,0 +1,46 @@ +package org.lflang.generator.cpp + +import org.lflang.capitalize +import org.lflang.lf.Reactor +import org.lflang.inferredType + + +val Reactor.allCppMessageTypes: Set + get() = with (this ){ + val s = inputs.map{ROSMsgType(it.inferredType.cppType)}.toMutableSet() + s.addAll(outputs.map{ROSMsgType(it.inferredType.cppType)}) + return s + } + +data class ROSMsgType (private val _cppUserType : String){ + + val cppUserType : String + get() = _cppUserType + + val wrappedMsgCppInclude : String + get() { + // std_msgs have an extra "_" which needs to be removed, ROS message names must follow this regex: '^[A-Z][A-Za-z0-9]*$' + var msgT = cppUserType.replace("::", "") + msgT = msgT.replace("_", "") + msgT = msgT.replaceFirstChar(Char::lowercase)+ "Wrapped.hpp\"" + msgT = msgT.map{ if (it.isUpperCase()) "_${it.lowercase()}" else it}.joinToString("") + return "#include \"lf_wrapped_msgs/msg/$msgT" + } + + val userTypeMsgInclude : String + get() { + return cppUserType.replace("::", "/").replace("msg/", "") + } + + val wrappedCppType : String + get() { + return "lf_wrapped_msgs::msg::" + cppUserType.replace("::", "").replace("_", "").capitalize() + "Wrapped" + } + + + val wrappedMsgFileName : String + get() { + return cppUserType.replace("::", "").replace("_", "").capitalize() + "Wrapped.msg" + } +} + diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt index 6ca3f3b635..3e0b5c2704 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt @@ -35,13 +35,14 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator val lfMsgsRosDir = "/lib/cpp/$lfMsgsRosPackageName" FileUtil.copyFromClassPath(lfMsgsRosDir, fileConfig.srcGenBasePath, true, false) - val messageTypesToWrap : MutableSet = mutableSetOf() + val rosMsgTypes : MutableSet = mutableSetOf() for (nodeGen in nodeGenerators) { - messageTypesToWrap.addAll(nodeGen.getMessageTypes()) + rosMsgTypes.addAll(nodeGen.reactor.allCppMessageTypes) } - val msgWrapGen = CppRos2MessageWrapperGenerator(messageTypesToWrap) + + val msgWrapGen = CppRos2MessageWrapperGenerator(rosMsgTypes) for ((messageFileName, messageFileContent) in msgWrapGen.generateMessageFiles()) { - FileUtil.writeToFile(messageFileContent, fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("msg").resolve("$messageFileName.msg")) + FileUtil.writeToFile(messageFileContent, fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("msg").resolve(messageFileName)) } FileUtil.writeToFile(msgWrapGen.generatePackageCmake(), fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("CMakeLists.txt")) FileUtil.writeToFile(msgWrapGen.generatePackageXml(), fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("package.xml")) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt index 854ec91c08..38713f7071 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt @@ -3,23 +3,32 @@ package org.lflang.generator.cpp import org.lflang.capitalize import org.lflang.joinWithLn -class CppRos2MessageWrapperGenerator (private val messageTypesToWrap : Set){ +class CppRos2MessageWrapperGenerator (private val messageTypesToWrap : Set){ + val ROSMsgType.fileContent : String + get() { + return """ + |lf_msgs_ros/Tag tag + |${"$userTypeMsgInclude message"} + """.trimMargin() + } + + val fileContents : List + get() { + return messageTypesToWrap.map{it.fileContent} + } fun generateMessageFiles() : List> { return messageTypesToWrap.map{ Pair( - // std_msgs have an extra "_" which needs to be removed, ROS message names must follow this regex: '^[A-Z][A-Za-z0-9]*$' - it.replace("::", "").replace("_", "").capitalize() + "Wrapped", - """ - |lf_msgs_ros/Tag tag - |${it.replace("::", "/").replace("msg/", "") + " message"} - """.trimMargin() + it.wrappedMsgFileName, + it.fileContent ) } } fun generatePackageCmake(): String { val S = '$' + messageTypesToWrap.forEach{ println(it.cppUserType) } return """ |cmake_minimum_required(VERSION 3.5) |project(lf_wrapped_msgs) @@ -28,7 +37,7 @@ class CppRos2MessageWrapperGenerator (private val messageTypesToWrap : Set { - val s = reactor.inputs.map{it.inferredType.cppType}.toMutableSet() - s.addAll(reactor.outputs.map{it.inferredType.cppType}) - return s - } fun generateHeader(): String { return """ @@ -27,9 +22,7 @@ class CppRos2NodeGenerator( |#include |#include "${reactor.name}.hh" |#include "reactor-cpp/reactor-cpp.hh" - |${getMessageTypes().map { msgType -> - ("#include \"lf_wrapped_msgs/msg/"+ msgType.replace("::", "").replace("_", "").replaceFirstChar(Char::lowercase)+ "Wrapped.hpp\"").map { if (it.isUpperCase()) "_${it.lowercase()}" else it}.joinToString("") - }.joinLn() } + |${reactor.allCppMessageTypes.map { it.wrappedMsgCppInclude }.joinLn() } | |#include "${fileConfig.getReactorHeaderPath(reactor).toUnixString()}" | @@ -40,9 +33,9 @@ class CppRos2NodeGenerator( | std::unique_ptr lf_env; | std::unique_ptr<${reactor.name}> lf_reactor; | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n"){ - "std::unique_ptr> ${it.name}_sub;" } } + "std::unique_ptr> ${it.name}_sub;" } } | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n"){ - "std::unique_ptr> ${it.name}_pub;" } } + "std::unique_ptr> ${it.name}_pub;" } } | // thread of the LF execution | std::thread lf_thread; | // an additional thread that we use for waiting for LF termination @@ -93,12 +86,12 @@ class CppRos2NodeGenerator( val conAndInd : Pair? = reactor.allConnections.map{ con -> Pair(con, con.rightPorts.indexOf(it as VarRef)) }.find { (_, index) -> index >= 0} outputConnectedToThisInput = if (conAndInd == null) "test" else conAndInd.first.leftPorts[conAndInd.second].container.name + conAndInd.first.leftPorts[conAndInd.second].name - return@joinToString "${it.name}_sub = std::make_unique>(" + + return@joinToString "${it.name}_sub = std::make_unique>(" + "\"$outputConnectedToThisInput\",\"${it.name}_sub\", lf_env.get(), false, std::chrono::nanoseconds(0));" + "${it.name}_sub->add_port(&lf_reactor->${it.name});" } } | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n") { - "${it.name}_pub = std::make_unique>(\"test\");" + + "${it.name}_pub = std::make_unique>(\"test\");" + "${it.name}_pub->set_port(&lf_reactor->${it.name});" } } | // assemble reactor program From 17da6304163c87b60cada7051fcf3a54e4d541d3 Mon Sep 17 00:00:00 2001 From: CS Date: Thu, 24 Aug 2023 13:53:02 +0200 Subject: [PATCH 09/18] generation of launch file, passing topic names to endpoints through it --- .../lflang/generator/cpp/CppRos2Extensions.kt | 3 +- .../lflang/generator/cpp/CppRos2Generator.kt | 3 + .../generator/cpp/CppRos2NodeGenerator.kt | 54 ++++++--- .../generator/cpp/CppRos2PackageGenerator.kt | 105 ++++++++++++++++++ 4 files changed, 147 insertions(+), 18 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt index ccef9afbfa..2f33380062 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt @@ -19,7 +19,7 @@ data class ROSMsgType (private val _cppUserType : String){ val wrappedMsgCppInclude : String get() { - // std_msgs have an extra "_" which needs to be removed, ROS message names must follow this regex: '^[A-Z][A-Za-z0-9]*$' + // std_msgs have an extra "_" which needs to be removed var msgT = cppUserType.replace("::", "") msgT = msgT.replace("_", "") msgT = msgT.replaceFirstChar(Char::lowercase)+ "Wrapped.hpp\"" @@ -40,6 +40,7 @@ data class ROSMsgType (private val _cppUserType : String){ val wrappedMsgFileName : String get() { + // ROS message file names must follow this regex: '^[A-Z][A-Za-z0-9]*$' return cppUserType.replace("::", "").replace("_", "").capitalize() + "Wrapped.msg" } } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt index 3e0b5c2704..b533f7fa2f 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt @@ -66,6 +66,9 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator packagePath.resolve("CMakeLists.txt"), true ) + FileUtil.writeToFile(packageGenerator.generateLaunchFile(), + packagePath.resolve("launch").resolve("default.launch.py"), + true) val scriptPath = fileConfig.binPath.resolve(fileConfig.name); FileUtil.writeToFile(packageGenerator.generateBinScript(), scriptPath) scriptPath.toFile().setExecutable(true); diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt index 26fae1287f..7737217692 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt @@ -32,9 +32,9 @@ class CppRos2NodeGenerator( |private: | std::unique_ptr lf_env; | std::unique_ptr<${reactor.name}> lf_reactor; - | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n"){ + | ${reactor.inputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()){ "std::unique_ptr> ${it.name}_sub;" } } - | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n"){ + | ${reactor.outputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()){ "std::unique_ptr> ${it.name}_pub;" } } | // thread of the LF execution | std::thread lf_thread; @@ -51,6 +51,20 @@ class CppRos2NodeGenerator( } fun generateSource(): String { + println(reactor.connections) + if (reactor.connections.isNotEmpty()) { + println(reactor.connections[0].name) + println(reactor.connections[0]) + println(reactor.connections[0].name) + + println(reactor.connections[0].leftPorts.first().name) + println(reactor.connections[0].leftPorts.first().container.name) + println(reactor.connections[0].leftPorts.first().variable.name) + println(reactor.connections[0].leftPorts.first().alias) + println(reactor.connections[0].rightPorts.first()) + } + println(reactor.name) + return """ |#include "$nodeName.hh" |#include "${reactor.name}.hh" @@ -77,22 +91,28 @@ class CppRos2NodeGenerator( | | // instantiate the main reactor | lf_reactor = std::make_unique<${reactor.name}> ("${reactor.name}", lf_env.get(), ${reactor.name}::Parameters{}); - | ${reactor.inputs.joinToString(separator = "\n", prefix = "//\n") - { - val outputConnectedToThisInput : String - println("Connections here") - println(reactor.connections) - println(reactor.name) - val conAndInd : Pair? = reactor.allConnections.map{ con -> Pair(con, con.rightPorts.indexOf(it as VarRef)) }.find { (_, index) -> index >= 0} - outputConnectedToThisInput = if (conAndInd == null) "test" - else conAndInd.first.leftPorts[conAndInd.second].container.name + conAndInd.first.leftPorts[conAndInd.second].name - return@joinToString "${it.name}_sub = std::make_unique>(" + - "\"$outputConnectedToThisInput\",\"${it.name}_sub\", lf_env.get(), false, std::chrono::nanoseconds(0));" + "${it.name}_sub->add_port(&lf_reactor->${it.name});" - } + | + | ${reactor.inputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) { + """ + | this->declare_parameter("${it.name}", "some default string here"); + | rclcpp::Parameter ${it.name}_topic_name_param = this->get_parameter("${it.name}"); + | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string(); + | RCLCPP_WARN_STREAM(this->get_logger(), ${it.name}_topic_name_string); + | ${it.name}_sub = std::make_unique>(${it.name}_topic_name_string,"${it.name}_sub", lf_env.get(), false, std::chrono::nanoseconds(0)); + | ${it.name}_sub->add_port(&lf_reactor->${it.name}); + """ + } } - | ${reactor.outputs.joinToString(separator = "\n", prefix = "//\n") { - "${it.name}_pub = std::make_unique>(\"test\");" + - "${it.name}_pub->set_port(&lf_reactor->${it.name});" } + | ${reactor.outputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) { + """ + | this->declare_parameter("${it.name}", "some default string here"); + | rclcpp::Parameter ${it.name}_topic_name_param = this->get_parameter("${it.name}"); + | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string(); + | RCLCPP_WARN_STREAM(this->get_logger(), ${it.name}_topic_name_string); + | ${it.name}_pub = std::make_unique>(${it.name}_topic_name_string); + | ${it.name}_pub->set_port(&lf_reactor->${it.name}); + """ + } } | // assemble reactor program | lf_env->assemble(); diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index 551072f8bf..52be6ad2fe 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -3,6 +3,7 @@ package org.lflang.generator.cpp import org.lflang.generator.PrependOperator import org.lflang.joinLn import org.lflang.joinWithLn +import org.lflang.reactor import org.lflang.toUnixString import java.nio.file.Path @@ -98,6 +99,8 @@ class CppRos2PackageGenerator(generator: CppGenerator) { | TARGETS ${it.nodeName} | DESTINATION lib/$S{PROJECT_NAME} | ) + |install(DIRECTORY launch + | DESTINATION share/$S{PROJECT_NAME}) | |if(MSVC) | target_compile_options(${it.nodeName} PRIVATE /W4) @@ -117,6 +120,108 @@ class CppRos2PackageGenerator(generator: CppGenerator) { } } + private fun createReactorStructurePython(gen : CppRos2NodeGenerator) : String { + var s = "Reactor(\"${gen.nodeName}\"" + s += "," + System.lineSeparator() + s+= "{" + var isFirst : Boolean = true + for (inst in gen.reactor.instantiations) { + if (isFirst) isFirst = false + else s+=", " + val instGen = nodeGenerators.filter{ it.reactor == inst.reactor}.first() + s+="\"${inst.name}\": (\"${instGen.nodeName}\", ${createReactorStructurePython(instGen)})" + } + s+= "}," + System.lineSeparator() + s+= "[" + for (con in gen.reactor.connections) { + s+="Connection(" + s+= "[" + isFirst = true + for (leftP in con.leftPorts) { + if (isFirst) isFirst = false + else s+=", " + s+= "[\"${leftP.container.name}\", \"${leftP.variable.name}\"]" + } + s+= "], " + s+= "[" + isFirst = true + for (rightP in con.rightPorts) { + if (isFirst) isFirst = false + else s+=", " + s+= "[\"${rightP.container.name}\", \"${rightP.variable.name}\"]" + } + s+= "]" + s+=")" + } + s+= "]" + s+= ")" + System.lineSeparator() + return s + } + + fun generateLaunchFile(): String { + val mainReactor = nodeGenerators.filter{ it.reactor.isMain}.first() + val reactorStructurePython = createReactorStructurePython(mainReactor) + return """ + |from __future__ import annotations + |from typing import List, Tuple, Dict + |from dataclasses import dataclass + |from launch import LaunchDescription + |from launch_ros.actions import Node + | + |class Connection: + | leftPorts: List[Tuple[str, str]] # Tuple consists of user-defined instantiation name and port name + | rightPorts: List[Tuple[str, str]] # Tuple consists of user-defined instantiation name and port name + | + | def __init__(self, lPorts, rPorts): + | self.leftPorts = lPorts + | self.rightPorts = rPorts + | + |class Reactor: + | classname: str + | fed_instantiations: Dict[str, Reactor] + | connections: List[Connection] + | + | def __init__(self, _classname, _fed_instantiations, _connections): + | self.classname = _classname + | self.fed_instantiations = _fed_instantiations + | self.connections = _connections + | + |# structure of reactors and connections defined here like so + |#mainR : Reactor = Reactor("main", + |# {"sub" : ("SubNode", Reactor("Sub", {}, [])), "pub": ("PubNode", Reactor("Pub", {}, []))}, + |# [Connection([["pub", "out"]], [["sub", "in"]])]) + |mainR : Reactor = $reactorStructurePython + | + |def generate_launch_description(): + | # main node is added first + | nodes : List[Node] = [Node(package='${fileConfig.name}', + | executable='${ nodeGenerators.filter{ it->it.reactor.isMain}.first().nodeName}', + | name='${ nodeGenerators.filter{ it->it.reactor.isMain}.first().nodeName}', + | parameters=[] + | )] + | instances_todo : List[Tuple[str, Reactor]] = [["", mainR]] # str is prefix for everything in this instance + | while instances_todo: + | [prefix, currentReactor] = instances_todo.pop(0) + | for inst in currentReactor.fed_instantiations.items(): + | port_params: Dict[str, str] = {} # params name is port name, param itself is the outward ports uniquely qualified name which is used for topic + | for c in currentReactor.connections: + | for i in range(len(c.rightPorts)): + | if c.leftPorts[i][0] == inst[0]: + | port_params[c.leftPorts[i][1]] = prefix+ "_" + c.leftPorts[i][1] + | if c.rightPorts[i][0] == inst[0]: + | port_params[c.rightPorts[i][1]] = prefix+ "_" + c.leftPorts[i][1] + | nodes.append(Node(package='${fileConfig.name}', + | executable=inst[1][0], + | name=prefix+ "_" + inst[0], + | parameters=[port_params]) + | ) + | instances_todo.append([prefix + "_" + inst[0], inst[1][1]]) + | + | return LaunchDescription(nodes) + """.trimMargin() + + } + fun generateBinScript(): String { val relPath = fileConfig.binPath.relativize(fileConfig.outPath).toUnixString() From 18fb27c6370954bed47988b9d6ff3b3974579498 Mon Sep 17 00:00:00 2001 From: CS Date: Sat, 7 Oct 2023 16:21:46 +0200 Subject: [PATCH 10/18] --- .../cpp/CppAssembleMethodGenerator.kt | 2 +- .../generator/cpp/CppInstanceGenerator.kt | 6 +- .../lflang/generator/cpp/CppRos2Extensions.kt | 33 +- .../lflang/generator/cpp/CppRos2Generator.kt | 6 +- .../cpp/CppRos2MessageWrapperGenerator.kt | 16 +- .../generator/cpp/CppRos2NodeGenerator.kt | 325 ++++++++-- .../generator/cpp/CppRos2PackageGenerator.kt | 203 ++++-- test/Cpp/install/.colcon_install_layout | 1 - test/Cpp/install/COLCON_IGNORE | 0 .../share/StringConnection/package.bash | 39 -- .../share/StringConnection/package.dsv | 5 - .../share/StringConnection/package.ps1 | 115 ---- .../share/StringConnection/package.sh | 86 --- .../share/StringConnection/package.zsh | 50 -- .../colcon-core/packages/StringConnection | 1 - .../share/VoidConnection/package.bash | 39 -- .../share/VoidConnection/package.dsv | 5 - .../share/VoidConnection/package.ps1 | 115 ---- .../share/VoidConnection/package.sh | 86 --- .../share/VoidConnection/package.zsh | 50 -- .../share/colcon-core/packages/VoidConnection | 1 - test/Cpp/install/_local_setup_util_ps1.py | 404 ------------ test/Cpp/install/_local_setup_util_sh.py | 404 ------------ test/Cpp/install/local_setup.bash | 107 ---- test/Cpp/install/local_setup.ps1 | 55 -- test/Cpp/install/local_setup.sh | 137 ---- test/Cpp/install/local_setup.zsh | 120 ---- .../reactor-cpp-default/reactor-cpp/action.hh | 206 ------ .../reactor-cpp-default/reactor-cpp/assert.hh | 104 --- .../reactor-cpp-default/reactor-cpp/config.hh | 13 - .../reactor-cpp/config.hh.in | 13 - .../reactor-cpp/connection.hh | 244 ------- .../reactor-cpp/connection_endpoint.hh | 83 --- .../reactor-cpp/environment.hh | 113 ---- .../reactor-cpp-default/reactor-cpp/fwd.hh | 31 - .../reactor-cpp/impl/action_impl.hh | 106 ---- .../reactor-cpp/impl/port_impl.hh | 51 -- .../reactor-cpp/logging.hh | 109 ---- .../reactor-cpp/logical_time.hh | 100 --- .../reactor-cpp/multiport.hh | 119 ---- .../reactor-cpp-default/reactor-cpp/port.hh | 178 ------ .../reactor-cpp/reaction.hh | 76 --- .../reactor-cpp/reactor-cpp.hh | 26 - .../reactor-cpp/reactor.hh | 97 --- .../reactor-cpp/ros2_connection_endpoint.hh | 79 --- .../reactor-cpp/safe_vector.hh | 62 -- .../reactor-cpp/scheduler.hh | 197 ------ .../reactor-cpp/semaphore.hh | 45 -- .../reactor-cpp/statistics.hh | 99 --- .../reactor-cpp-default/reactor-cpp/time.hh | 35 - .../reactor-cpp/time_barrier.hh | 77 --- .../reactor-cpp-default/reactor-cpp/trace.hh | 92 --- .../reactor-cpp/value_ptr.hh | 599 ------------------ .../lib/libreactor-cpp-default.so | 1 - .../lib/libreactor-cpp-default.so.0.0.1 | Bin 175976 -> 0 bytes .../lib/libreactor-cpp-default.so.1 | 1 - .../colcon-core/packages/reactor-cpp-default | 1 - .../reactor-cpp-defaultConfig-release.cmake | 19 - .../cmake/reactor-cpp-defaultConfig.cmake | 107 ---- .../hook/cmake_prefix_path.dsv | 1 - .../hook/cmake_prefix_path.ps1 | 3 - .../hook/cmake_prefix_path.sh | 3 - .../hook/ld_library_path_lib.dsv | 1 - .../hook/ld_library_path_lib.ps1 | 3 - .../hook/ld_library_path_lib.sh | 3 - .../share/reactor-cpp-default/package.bash | 31 - .../share/reactor-cpp-default/package.dsv | 6 - .../share/reactor-cpp-default/package.ps1 | 116 ---- .../share/reactor-cpp-default/package.sh | 87 --- .../share/reactor-cpp-default/package.zsh | 42 -- test/Cpp/install/setup.bash | 31 - test/Cpp/install/setup.ps1 | 29 - test/Cpp/install/setup.sh | 45 -- test/Cpp/install/setup.zsh | 31 - test/Cpp/src/ros-federated/FederateCycle.lf | 76 +++ .../FederateEmptyConnection.lf} | 36 +- .../FederateHierarchicalConnections.lf | 69 ++ .../FederateHierarchicalConnections2.lf | 97 +++ .../src/ros-federated/FederateHierarchy.lf | 82 +++ .../FederateMultipleReactions.lf | 88 +++ .../ros-federated/FederateStringConnection.lf | 74 +++ .../FederateStringConnectionDelayed.lf | 74 +++ .../FederateStringConnectionPhysical.lf | 74 +++ .../StringConnection.lf | 24 +- 84 files changed, 1158 insertions(+), 5462 deletions(-) delete mode 100644 test/Cpp/install/.colcon_install_layout delete mode 100644 test/Cpp/install/COLCON_IGNORE delete mode 100644 test/Cpp/install/StringConnection/share/StringConnection/package.bash delete mode 100644 test/Cpp/install/StringConnection/share/StringConnection/package.dsv delete mode 100644 test/Cpp/install/StringConnection/share/StringConnection/package.ps1 delete mode 100644 test/Cpp/install/StringConnection/share/StringConnection/package.sh delete mode 100644 test/Cpp/install/StringConnection/share/StringConnection/package.zsh delete mode 100644 test/Cpp/install/StringConnection/share/colcon-core/packages/StringConnection delete mode 100644 test/Cpp/install/VoidConnection/share/VoidConnection/package.bash delete mode 100644 test/Cpp/install/VoidConnection/share/VoidConnection/package.dsv delete mode 100644 test/Cpp/install/VoidConnection/share/VoidConnection/package.ps1 delete mode 100644 test/Cpp/install/VoidConnection/share/VoidConnection/package.sh delete mode 100644 test/Cpp/install/VoidConnection/share/VoidConnection/package.zsh delete mode 100644 test/Cpp/install/VoidConnection/share/colcon-core/packages/VoidConnection delete mode 100644 test/Cpp/install/_local_setup_util_ps1.py delete mode 100644 test/Cpp/install/_local_setup_util_sh.py delete mode 100644 test/Cpp/install/local_setup.bash delete mode 100644 test/Cpp/install/local_setup.ps1 delete mode 100644 test/Cpp/install/local_setup.sh delete mode 100644 test/Cpp/install/local_setup.zsh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/action.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/assert.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh.in delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection_endpoint.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/environment.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/fwd.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/action_impl.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/port_impl.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logging.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logical_time.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/multiport.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/port.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reaction.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor-cpp.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/ros2_connection_endpoint.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/safe_vector.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/scheduler.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/semaphore.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/statistics.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time_barrier.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/trace.hh delete mode 100644 test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/value_ptr.hh delete mode 120000 test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so delete mode 100644 test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so.0.0.1 delete mode 120000 test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so.1 delete mode 100644 test/Cpp/install/reactor-cpp-default/share/colcon-core/packages/reactor-cpp-default delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig-release.cmake delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig.cmake delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.dsv delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.ps1 delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.sh delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.dsv delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.ps1 delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.sh delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.bash delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.dsv delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.ps1 delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.sh delete mode 100644 test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.zsh delete mode 100644 test/Cpp/install/setup.bash delete mode 100644 test/Cpp/install/setup.ps1 delete mode 100644 test/Cpp/install/setup.sh delete mode 100644 test/Cpp/install/setup.zsh create mode 100644 test/Cpp/src/ros-federated/FederateCycle.lf rename test/Cpp/src/{federated/EmptyConnection.lf => ros-federated/FederateEmptyConnection.lf} (51%) create mode 100644 test/Cpp/src/ros-federated/FederateHierarchicalConnections.lf create mode 100644 test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf create mode 100644 test/Cpp/src/ros-federated/FederateHierarchy.lf create mode 100644 test/Cpp/src/ros-federated/FederateMultipleReactions.lf create mode 100644 test/Cpp/src/ros-federated/FederateStringConnection.lf create mode 100644 test/Cpp/src/ros-federated/FederateStringConnectionDelayed.lf create mode 100644 test/Cpp/src/ros-federated/FederateStringConnectionPhysical.lf rename test/Cpp/src/{federated => ros-federated}/StringConnection.lf (63%) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt index 1a12f6424a..56cc8f75a9 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppAssembleMethodGenerator.kt @@ -212,7 +212,7 @@ class CppAssembleMethodGenerator(private val reactor: Reactor) { * The body of this method will declare all triggers, dependencies and antidependencies to the runtime. */ fun generateDefinition() = with(PrependOperator) { - val indexedConnections = reactor.connections.withIndex() + val indexedConnections = reactor.connections.filter { !it.isFederateConnection }.withIndex() """ |${reactor.templateLine} |void ${reactor.templateName}::assemble() { diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt index 19466c2fce..029e90d498 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppInstanceGenerator.kt @@ -135,13 +135,13 @@ class CppInstanceGenerator( /** Generate C++ include statements for each reactor that is instantiated */ fun generateIncludes(): String = - reactor.instantiations.map { fileConfig.getReactorHeaderPath(it.reactor) } + reactor.instantiations.filter { !AttributeUtils.isFederate(it) }.map { fileConfig.getReactorHeaderPath(it.reactor) } .distinct() .joinToString(separator = "\n") { """#include "${it.toUnixString()}" """ } /** Generate declaration statements for all reactor instantiations */ fun generateDeclarations(): String { - return reactor.instantiations.joinToString( + return reactor.instantiations.filter { !AttributeUtils.isFederate(it) }.joinToString( prefix = "// reactor instances\n", separator = "\n" ) { generateDeclaration(it) } @@ -152,6 +152,6 @@ class CppInstanceGenerator( /** Generate constructor initializers for all reactor instantiations */ fun generateInitializers(): String = - reactor.instantiations.mapNotNull { generateInitializer(it) } + reactor.instantiations.filter { !AttributeUtils.isFederate(it) }.mapNotNull { generateInitializer(it) } .joinToString(prefix = "//reactor instances\n", separator = "\n") } diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt index 2f33380062..8ae85f9252 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt @@ -1,15 +1,24 @@ package org.lflang.generator.cpp -import org.lflang.capitalize -import org.lflang.lf.Reactor -import org.lflang.inferredType +import org.lflang.* +import org.lflang.ast.ASTUtils +import org.lflang.generator.cpp.CppInstanceGenerator.Companion.isEnclave +import org.lflang.generator.cpp.CppTypes.getTargetTimeExpr +import org.lflang.lf.* val Reactor.allCppMessageTypes: Set get() = with (this ){ - val s = inputs.map{ROSMsgType(it.inferredType.cppType)}.toMutableSet() - s.addAll(outputs.map{ROSMsgType(it.inferredType.cppType)}) - return s + val reactors : MutableList = mutableListOf(this) + val types : MutableSet = mutableSetOf() + while (reactors.isNotEmpty()) { + val r = reactors.removeFirst() + types.addAll(r.inputs.map{ROSMsgType(it.inferredType.cppType)}) + types.addAll(r.outputs.map{ROSMsgType(it.inferredType.cppType)}) + for (inst in r.instantiations) reactors.add(inst.reactor) + } + + return types } data class ROSMsgType (private val _cppUserType : String){ @@ -45,3 +54,15 @@ data class ROSMsgType (private val _cppUserType : String){ } } +val Connection.isFederateConnection: Boolean + get() { + for (port in leftPorts + rightPorts) { + if (port.container != null && AttributeUtils.isFederate(port.container)) { + return true + } + } + return false + } + + + diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt index b533f7fa2f..51f0482805 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt @@ -2,6 +2,8 @@ package org.lflang.generator.cpp import org.lflang.AttributeUtils import org.lflang.generator.LFGeneratorContext +import org.lflang.lf.Input +import org.lflang.lf.Output import org.lflang.reactor import org.lflang.util.FileUtil import java.nio.file.Path @@ -11,11 +13,13 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator override val srcGenPath: Path = generator.fileConfig.srcGenPath.resolve("src") private val packagePath: Path = generator.fileConfig.srcGenPath - private val nodeGenerators : MutableList = mutableListOf(CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig)) + private val nodeGenerators : MutableList = mutableListOf() private val packageGenerator = CppRos2PackageGenerator(generator) private val lfMsgsRosPackageName = "lf_msgs_ros" override fun generatePlatformFiles() { + + nodeGenerators.add(CppRos2NodeGenerator(mainReactor, targetConfig, fileConfig)) val reactorsToSearch : MutableList = mutableListOf(mainReactor) /** Recursively searching for federates */ while (reactorsToSearch.isNotEmpty()) { diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt index 38713f7071..bd9473c8f6 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2MessageWrapperGenerator.kt @@ -29,19 +29,23 @@ class CppRos2MessageWrapperGenerator (private val messageTypesToWrap : Set>> = mutableListOf(Triple("", reactor, listOf())) + while (todo.isNotEmpty()) { + val (prefix, r, preInst) = todo.removeFirst() + + for (inst in r.instantiations) { + if (!AttributeUtils.isFederate(inst)) todo.add(Triple(prefix + "/" + inst.name, inst.reactor, preInst + inst)) + } + for (con in r.connections) { + for (index in con.leftPorts.indices) { + val l : VarRef = con.leftPorts[index] + val r : VarRef = con.rightPorts[index] + if (l.container != null && !AttributeUtils.isFederate(l.container) + && r.container != null && AttributeUtils.isFederate(r.container)) { + val lPort = l.variable as Output + var lPortVarName = prefix + l.container.name + "_" + lPort.name + var topic_name = prefix + l.container.name + "/" + lPort.name + if (con.isPhysical) { + lPortVarName += "_physical" + subReactorEndpointDeclarations += System.lineSeparator() + + "| std::unique_ptr> $lPortVarName;" + + subReactorEndpointInitializers += """ + | $lPortVarName = std::make_unique>(lf_federate_prefix + "/$topic_name"); + | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor physical endpoint $lPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); + """ + } + else { + subReactorEndpointDeclarations += System.lineSeparator() + + "| std::unique_ptr> $lPortVarName;" + + subReactorEndpointInitializers += """ + | $lPortVarName = std::make_unique>(lf_federate_prefix + "/$topic_name"); + | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor endpoint $lPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); + """ + } + + subReactorEndpointInitializers += """ + | reactor::Reactor* ${lPortVarName}_reactor = lf_reactor.get(); + | bool ${lPortVarName}_subreactor_found; + | ${(preInst + l.container).joinToString(separator = System.lineSeparator()) { + """ + | ${lPortVarName}_subreactor_found = false; + | for(auto r : ${lPortVarName}_reactor->reactors()) + | if (r->name() == "${it.name}") { + | ${lPortVarName}_subreactor_found = true; + | ${lPortVarName}_reactor = r; + | } + | if (!${lPortVarName}_subreactor_found) + | RCLCPP_ERROR(this->get_logger(), "Failed to find subreactor \"${it.name}\""); + """ + } + } + | $lPortVarName->set_port(&dynamic_cast<${l.container.reactor.name}*>(${lPortVarName}_reactor)->${lPort.name}); + """ + } + if (r.container != null && !AttributeUtils.isFederate(r.container) + && l.container != null && AttributeUtils.isFederate(l.container)) { + val rPort = r.variable as Input + var rPortVarName = prefix + r.container.name + "_" + rPort.name + var topic_name= prefix + l.container.name + "/" + (l.variable as Port).name + // if the container is a federate where the out port is remapped check until we find one that should not be remapped + if (AttributeUtils.isFederate(l.container)) { + topic_name = prefix + var prev_l = l + while(AttributeUtils.isFederate(prev_l.container)) { + topic_name += prev_l.container.name + var next_l : VarRef? = null; + for (fed_con in prev_l.container.reactor.connections) { + for ((fed_con_index, fed_con_rPort) in fed_con.rightPorts.withIndex()) { + if (fed_con_rPort.name == (prev_l.variable as Port).name && fed_con_rPort.container == null) + next_l = fed_con.leftPorts[fed_con_index] + } + } + if (next_l == null) { + topic_name += "/" +(prev_l.variable as Port).name + break + } + else { + topic_name += "/" + prev_l = next_l + next_l = null + } + } + } + + if (con.isPhysical) { + rPortVarName += "_physical" + subReactorEndpointDeclarations += System.lineSeparator() + + "| std::unique_ptr> $rPortVarName;" + + subReactorEndpointInitializers += """ + | $rPortVarName = std::make_unique> + | (lf_federate_prefix + "/$topic_name","${rPortVarName}_sub", lf_env.get() + | ${if (con.delay != null) ", " + con.delay.toCppTime() else ""}); + | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor physical endpoint $rPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); + """ + } + else { + subReactorEndpointDeclarations += System.lineSeparator() + + "| std::unique_ptr> $rPortVarName;" + + subReactorEndpointInitializers += """ + | $rPortVarName = std::make_unique> + | (lf_federate_prefix + "/$topic_name", "${rPortVarName}_sub",lf_env.get() + | ${if (con.delay != null) ", " + con.delay.toCppTime() else ""}); + | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor endpoint $rPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); + """ + } + + subReactorEndpointInitializers += """ + | reactor::Reactor* ${rPortVarName}_reactor = lf_reactor.get(); + | bool ${rPortVarName}_subreactor_found; + | ${(preInst + r.container).joinToString(separator = System.lineSeparator()) { + """ + | ${rPortVarName}_subreactor_found = false; + | for(auto r : ${rPortVarName}_reactor->reactors()) + | if (r->name() == "${it.name}") { + | ${rPortVarName}_subreactor_found = true; + | ${rPortVarName}_reactor = r; + | } + | if (!${rPortVarName}_subreactor_found) + | RCLCPP_ERROR(this->get_logger(), "Failed to find subreactor \"${it.name}\""); + """ + } + } + | $rPortVarName->add_port(&dynamic_cast<${r.container.reactor.name}*>(${rPortVarName}_reactor)->${rPort.name}); + """ + } + + } + + } + + + } + } + + + private fun generateEndpointDeclarations(): String { + // for now we dont care if the outputs/inputs of this federate are used in physical or non-physical connections + // hence both are generated and initialized based on parameters of launch script + // maybe TODO: one could search for any connections of this federate starting from main reactor + // to figure out if and how this federates' ports are used and then generate only what is needed + // note: for banks/multiports would somehow need to know what exact instance this is (maybe via launch params) + return """ + | // toplevel federates' input and output endpoints + | ${reactor.inputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) { + """ + | std::unique_ptr> ${it.name}; + | std::unique_ptr> ${it.name}_physical; + """ + } + } + | ${reactor.outputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) { + """ + | std::unique_ptr> ${it.name}; + | std::unique_ptr> ${it.name}_physical; + """ + } + } + | + | // endPoint declarations for this federates' subreactors + $subReactorEndpointDeclarations + """ + } + fun generateHeader(): String { return """ @@ -22,6 +210,9 @@ class CppRos2NodeGenerator( |#include |#include "${reactor.name}.hh" |#include "reactor-cpp/reactor-cpp.hh" + |#include + |#include "reactor-cpp/ros2_connection_endpoint.hh" + | |${reactor.allCppMessageTypes.map { it.wrappedMsgCppInclude }.joinLn() } | |#include "${fileConfig.getReactorHeaderPath(reactor).toUnixString()}" @@ -32,10 +223,18 @@ class CppRos2NodeGenerator( |private: | std::unique_ptr lf_env; | std::unique_ptr<${reactor.name}> lf_reactor; - | ${reactor.inputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()){ - "std::unique_ptr> ${it.name}_sub;" } } - | ${reactor.outputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()){ - "std::unique_ptr> ${it.name}_pub;" } } + | std::string lf_federate_prefix; + | static const std::string LF_FEDERATE_PREFIX_PARAM_NAME; + | ${ + if (reactor.isMain) """ + | std::shared_ptr> start_time_publisher; + """ else """ + | std::shared_ptr> start_time_subscription; + """ + } + | + | ${generateEndpointDeclarations()} + | | // thread of the LF execution | std::thread lf_thread; | // an additional thread that we use for waiting for LF termination @@ -51,19 +250,8 @@ class CppRos2NodeGenerator( } fun generateSource(): String { - println(reactor.connections) - if (reactor.connections.isNotEmpty()) { - println(reactor.connections[0].name) - println(reactor.connections[0]) - println(reactor.connections[0].name) - - println(reactor.connections[0].leftPorts.first().name) - println(reactor.connections[0].leftPorts.first().container.name) - println(reactor.connections[0].leftPorts.first().variable.name) - println(reactor.connections[0].leftPorts.first().alias) - println(reactor.connections[0].rightPorts.first()) - } - println(reactor.name) + // maybe TODO: naming a reactor "Node" leads to naming collisions with the rclcpp::Node + // for now just dont do that return """ |#include "$nodeName.hh" @@ -73,12 +261,16 @@ class CppRos2NodeGenerator( |#include | |void $nodeName::wait_for_lf_shutdown() { + | RCLCPP_DEBUG_STREAM(this->get_logger(), "$nodeName waiting before shutting down"); | lf_thread.join(); + | RCLCPP_DEBUG_STREAM(this->get_logger(), "$nodeName shutting down"); | this->get_node_options().context()->shutdown("LF execution terminated"); |} | + |const std::string $nodeName::LF_FEDERATE_PREFIX_PARAM_NAME = "lf_federate_prefix"; + | |$nodeName::$nodeName(const rclcpp::NodeOptions& node_options = rclcpp::NodeOptions()) - | : Node("$nodeName", node_options) { + | : rclcpp::Node("$nodeName", node_options) { | unsigned workers = ${if (targetConfig.workers != 0) targetConfig.workers else "std::thread::hardware_concurrency()"}; | bool fast{${targetConfig.fastMode}}; | reactor::Duration lf_timeout{${targetConfig.timeout?.toCppCode() ?: "reactor::Duration::max()"}}; @@ -91,35 +283,94 @@ class CppRos2NodeGenerator( | | // instantiate the main reactor | lf_reactor = std::make_unique<${reactor.name}> ("${reactor.name}", lf_env.get(), ${reactor.name}::Parameters{}); + | + | this->declare_parameter(LF_FEDERATE_PREFIX_PARAM_NAME); + | rclcpp::Parameter lf_federate_prefix_param; + | if (this->get_parameter(LF_FEDERATE_PREFIX_PARAM_NAME, lf_federate_prefix_param)) + | lf_federate_prefix = lf_federate_prefix_param.as_string(); + | else RCLCPP_WARN_STREAM(this->get_logger(), "parameter \"" + LF_FEDERATE_PREFIX_PARAM_NAME + "\" missing"); + | | | ${reactor.inputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) { """ - | this->declare_parameter("${it.name}", "some default string here"); - | rclcpp::Parameter ${it.name}_topic_name_param = this->get_parameter("${it.name}"); - | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string(); - | RCLCPP_WARN_STREAM(this->get_logger(), ${it.name}_topic_name_string); - | ${it.name}_sub = std::make_unique>(${it.name}_topic_name_string,"${it.name}_sub", lf_env.get(), false, std::chrono::nanoseconds(0)); - | ${it.name}_sub->add_port(&lf_reactor->${it.name}); + | this->declare_parameter("${it.name}"); + | rclcpp::Parameter ${it.name}_topic_name_param; + | if (this->get_parameter("${it.name}", ${it.name}_topic_name_param)) { + | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string(); + | rclcpp::Parameter ${it.name}_delay; + | this->declare_parameter("${it.name}_delay"); + | if (this->get_parameter("${it.name}_delay", ${it.name}_delay)) + | ${it.name} = std::make_unique>(${it.name}_topic_name_string,"${it.name}_sub", lf_env.get(), std::chrono::nanoseconds(${it.name}_delay.as_int())); + | else ${it.name} = std::make_unique>(${it.name}_topic_name_string,"${it.name}_sub", lf_env.get()); + | ${it.name}->add_port(&lf_reactor->${it.name}); + | } + | + | this->declare_parameter("${it.name}_physical"); + | rclcpp::Parameter ${it.name}_physical_topic_name_param; + | if (this->get_parameter("${it.name}_physical", ${it.name}_physical_topic_name_param)) { + | std::string ${it.name}_physical_topic_name_string = ${it.name}_physical_topic_name_param.as_string(); + | rclcpp::Parameter ${it.name}_delay; + | this->declare_parameter("${it.name}_physical_delay"); + | if (this->get_parameter("${it.name}_physical_delay", ${it.name}_delay)) + | ${it.name}_physical = std::make_unique>(${it.name}_physical_topic_name_string,"${it.name}_physical_sub", lf_env.get(), std::chrono::nanoseconds(${it.name}_delay.as_int())); + | else ${it.name}_physical = std::make_unique>(${it.name}_physical_topic_name_string,"${it.name}_physical_sub", lf_env.get()); + | ${it.name}_physical->add_port(&lf_reactor->${it.name}); + | } """ } } | ${reactor.outputs.joinToString(separator = System.lineSeparator(), prefix = "//" + System.lineSeparator()) { """ - | this->declare_parameter("${it.name}", "some default string here"); - | rclcpp::Parameter ${it.name}_topic_name_param = this->get_parameter("${it.name}"); - | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string(); - | RCLCPP_WARN_STREAM(this->get_logger(), ${it.name}_topic_name_string); - | ${it.name}_pub = std::make_unique>(${it.name}_topic_name_string); - | ${it.name}_pub->set_port(&lf_reactor->${it.name}); + | this->declare_parameter("${it.name}"); + | rclcpp::Parameter ${it.name}_topic_name_param; + | if (this->get_parameter("${it.name}", ${it.name}_topic_name_param)) { + | std::string ${it.name}_topic_name_string = ${it.name}_topic_name_param.as_string(); + | ${it.name} = std::make_unique>(${it.name}_topic_name_string); + | ${it.name}->set_port(&lf_reactor->${it.name}); + | } + | + | this->declare_parameter("${it.name}_physical"); + | rclcpp::Parameter ${it.name}_physical_topic_name_param; + | if (this->get_parameter("${it.name}_physical", ${it.name}_physical_topic_name_param)) { + | std::string ${it.name}_physical_topic_name_string = ${it.name}_physical_topic_name_param.as_string(); + | ${it.name}_physical = std::make_unique>(${it.name}_physical_topic_name_string); + | ${it.name}_physical->set_port(&lf_reactor->${it.name}); + | } """ } } + $subReactorEndpointInitializers | // assemble reactor program | lf_env->assemble(); - | - | // start execution - | lf_thread = lf_env->startup(); - | lf_shutdown_thread = std::thread([this] { wait_for_lf_shutdown(); }); + | + | const std::string LF_STARTUP_TIME_TOPIC = "__lf_startup_time__"; + | ${ if (reactor.isMain) """ + | reactor::TimePoint start_time = reactor::get_physical_time(); + | start_time_publisher = this->create_publisher(LF_STARTUP_TIME_TOPIC, rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local()); + | { + | std_msgs::msg::Int64 time_point_message; + | time_point_message.data = start_time.time_since_epoch().count(); + | start_time_publisher->publish(time_point_message); + | } + | + | // start execution + | lf_thread = lf_env->startup(start_time); + | lf_shutdown_thread = std::thread([this] { wait_for_lf_shutdown(); }); + """ else """ + | start_time_subscription = this->create_subscription(LF_STARTUP_TIME_TOPIC, rclcpp::QoS(rclcpp::KeepLast(1)).reliable().transient_local(), + | [&](const std_msgs::msg::Int64::SharedPtr msg) { + | reactor::TimePoint start_time(std::chrono::nanoseconds(msg->data)); + | // start execution + | lf_thread = lf_env->startup(start_time); + | lf_shutdown_thread = std::thread([this] { wait_for_lf_shutdown(); }); + | start_time_subscription.reset(); + | }); + """ } + | |} | |$nodeName::~$nodeName() { diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index 52be6ad2fe..eb9daa5dc7 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -1,8 +1,12 @@ package org.lflang.generator.cpp +import org.lflang.AttributeUtils +import org.lflang.ast.ASTUtils import org.lflang.generator.PrependOperator import org.lflang.joinLn import org.lflang.joinWithLn +import org.lflang.lf.Input +import org.lflang.lf.Reactor import org.lflang.reactor import org.lflang.toUnixString import java.nio.file.Path @@ -86,11 +90,8 @@ class CppRos2PackageGenerator(generator: CppGenerator) { ${ nodeGenerators.map { """ |add_executable(${it.nodeName} - ${ nodeGenerators.map { it1 -> - """| src/${fileConfig.srcGenBasePath.relativize(fileConfig.srcGenPkgPath).toUnixString()}/${it1.reactor.name}.cc""" - }.joinLn() } | src/${it.nodeName}.cc - | + ${" | src/"..sources.joinWithLn { it.toUnixString() }} |) |ament_target_dependencies(${it.nodeName} ${dependencies.joinToString(" ")} lf_msgs_ros lf_wrapped_msgs) |target_link_libraries(${it.nodeName} $reactorCppName) @@ -120,27 +121,38 @@ class CppRos2PackageGenerator(generator: CppGenerator) { } } - private fun createReactorStructurePython(gen : CppRos2NodeGenerator) : String { - var s = "Reactor(\"${gen.nodeName}\"" + private fun createReactorStructurePython(reactor : Reactor, nodeName : String = "") : String { + var s = "Reactor(\"$nodeName\"" s += "," + System.lineSeparator() - s+= "{" + s+= "[" var isFirst : Boolean = true - for (inst in gen.reactor.instantiations) { + for (inst in reactor.instantiations) { if (isFirst) isFirst = false else s+=", " - val instGen = nodeGenerators.filter{ it.reactor == inst.reactor}.first() - s+="\"${inst.name}\": (\"${instGen.nodeName}\", ${createReactorStructurePython(instGen)})" + s+="Instantiation(\"${inst.name}\", " + if (AttributeUtils.isFederate(inst)) { + val instGen = nodeGenerators.filter{ it.reactor == inst.reactor}.first() + s+="\"${instGen.nodeName}\", ${createReactorStructurePython(inst.reactor, instGen.nodeName)}" + } else { + s+="None, ${createReactorStructurePython(inst.reactor)}" + } + s+= ")" } - s+= "}," + System.lineSeparator() + s+= "]," + System.lineSeparator() s+= "[" - for (con in gen.reactor.connections) { + isFirst = true + for (con in reactor.connections) { + if (isFirst) isFirst = false + else s+=", " s+="Connection(" s+= "[" isFirst = true for (leftP in con.leftPorts) { if (isFirst) isFirst = false else s+=", " - s+= "[\"${leftP.container.name}\", \"${leftP.variable.name}\"]" + + s+= "Port(${if (leftP.container != null) "\"${leftP.container.name}\"" else "None" }" + + ", \"${leftP.variable.name}\", ${if (leftP.variable is Input) "True" else "False"})" } s+= "], " s+= "[" @@ -148,9 +160,13 @@ class CppRos2PackageGenerator(generator: CppGenerator) { for (rightP in con.rightPorts) { if (isFirst) isFirst = false else s+=", " - s+= "[\"${rightP.container.name}\", \"${rightP.variable.name}\"]" + s+= "Port(${if (rightP.container != null) "\"${rightP.container.name}\"" else "None" }, " + + "\"${rightP.variable.name}\", ${if (rightP.variable is Input) "True" else "False"})" } - s+= "]" + s+= "], " + s+= if (con.isPhysical) "True" else "False" + s+= ", " + s+= if (con.delay != null) ASTUtils.getLiteralTimeValue(con.delay).toNanoSeconds() else "None" s+=")" } s+= "]" @@ -159,64 +175,145 @@ class CppRos2PackageGenerator(generator: CppGenerator) { } fun generateLaunchFile(): String { - val mainReactor = nodeGenerators.filter{ it.reactor.isMain}.first() - val reactorStructurePython = createReactorStructurePython(mainReactor) + val mainReactorNodeGen = nodeGenerators.filter{ it.reactor.isMain}.first() + val reactorStructurePython = createReactorStructurePython(mainReactorNodeGen.reactor) return """ |from __future__ import annotations - |from typing import List, Tuple, Dict + |from typing import List, Tuple, Dict, Optional |from dataclasses import dataclass |from launch import LaunchDescription |from launch_ros.actions import Node | + |class Port: + | instance_name: str + | name: str + | is_input: bool + | + | def __init__(self, _inst_name, _port_name, _is_input): + | self.instance_name = _inst_name + | self.name = _port_name + | self.is_input = _is_input + | + | |class Connection: - | leftPorts: List[Tuple[str, str]] # Tuple consists of user-defined instantiation name and port name - | rightPorts: List[Tuple[str, str]] # Tuple consists of user-defined instantiation name and port name + | leftPorts: List[Port] + | rightPorts: List[Port] + | physical: bool + | delay: Optional[long] | - | def __init__(self, lPorts, rPorts): + | def __init__(self, lPorts, rPorts, isPhysical, _delay): | self.leftPorts = lPorts | self.rightPorts = rPorts + | self.physical = isPhysical + | self.delay = _delay + | + |class Instantiation: + | name: str + | executable: str + | reactor: Reactor + | + | def __init__(self, _name, _executable, _reactor): + | self.name = _name + | self.executable = _executable + | self.reactor = _reactor | |class Reactor: | classname: str - | fed_instantiations: Dict[str, Reactor] + | instantiations: List[Instantiation] | connections: List[Connection] | - | def __init__(self, _classname, _fed_instantiations, _connections): + | def __init__(self, _classname, _instantiations, _connections): | self.classname = _classname - | self.fed_instantiations = _fed_instantiations + | self.instantiations = _instantiations | self.connections = _connections | - |# structure of reactors and connections defined here like so - |#mainR : Reactor = Reactor("main", - |# {"sub" : ("SubNode", Reactor("Sub", {}, [])), "pub": ("PubNode", Reactor("Pub", {}, []))}, - |# [Connection([["pub", "out"]], [["sub", "in"]])]) - |mainR : Reactor = $reactorStructurePython + |mainInstance : Reactor = Instantiation("${mainReactorNodeGen.reactor.name}", "${mainReactorNodeGen.nodeName}", + |$reactorStructurePython + |) + | + |def get_instance_by_prefix(prefix: str) -> Instantiation: + | if prefix[:len("/${mainReactorNodeGen.reactor.name}")] != "/${mainReactorNodeGen.reactor.name}": + | raise RuntimeError("prefix must start with /${mainReactorNodeGen.reactor.name}") + | prefix_without_main = prefix[len("/${mainReactorNodeGen.reactor.name}"):] + | splits = prefix_without_main.split("/") + | if not splits: + | return mainInstance + | inst = mainInstance + | if splits[:1][0] != "": + | raise RuntimeError("prefix is incorrect") + | for split in splits: + | filter_res = filter(lambda x: x.name == split, inst.reactor.instantiations) + | if len(filter_res) != 1: + | raise RuntimeError("instance " + split +" not found") + | else: + | inst = filter_res[0] + | return inst + | | |def generate_launch_description(): - | # main node is added first - | nodes : List[Node] = [Node(package='${fileConfig.name}', - | executable='${ nodeGenerators.filter{ it->it.reactor.isMain}.first().nodeName}', - | name='${ nodeGenerators.filter{ it->it.reactor.isMain}.first().nodeName}', - | parameters=[] - | )] - | instances_todo : List[Tuple[str, Reactor]] = [["", mainR]] # str is prefix for everything in this instance + | + | prefix_param_dict : Dict[str, Dict[str,str]] = {} + | container_prefix_dict : Dict[str, str] = {} # key: instance prefix, # value: container prefix + | instances_todo : List[Tuple[str, Instantiation, List[Connection]]] = [["", mainInstance, []]] # str is container prefix, list is connections from container regarding this instance | while instances_todo: - | [prefix, currentReactor] = instances_todo.pop(0) - | for inst in currentReactor.fed_instantiations.items(): - | port_params: Dict[str, str] = {} # params name is port name, param itself is the outward ports uniquely qualified name which is used for topic - | for c in currentReactor.connections: - | for i in range(len(c.rightPorts)): - | if c.leftPorts[i][0] == inst[0]: - | port_params[c.leftPorts[i][1]] = prefix+ "_" + c.leftPorts[i][1] - | if c.rightPorts[i][0] == inst[0]: - | port_params[c.rightPorts[i][1]] = prefix+ "_" + c.leftPorts[i][1] + | [prefix, instance, connections] = instances_todo.pop(0) + | lf_federate_prefix = prefix + "/" + instance.name + | # add next instances to list + | for next_inst in instance.reactor.instantiations: + | next_conns = [] + | for con in instance.reactor.connections: + | for i in range(len(con.rightPorts)): + | if con.leftPorts[i].instance_name == next_inst.name or con.rightPorts[i].instance_name == next_inst.name: + | next_conns.append(con) + | instances_todo.append((lf_federate_prefix, next_inst, next_conns)) + | # create node parameters + | + | container_prefix_dict[lf_federate_prefix] = prefix + | prefix_param_dict[lf_federate_prefix] = instance_param_dict = {} + | instance_param_dict["lf_federate_prefix"] = lf_federate_prefix + | for con in connections: + | physical_string = "_physical" if con.physical else "" + | for i in range(len(con.rightPorts)): + | leftP = con.leftPorts[i] + | rightP = con.rightPorts[i] + | if leftP.instance_name == instance.name and not leftP.is_input: + | instance_param_dict[leftP.name + physical_string] = lf_federate_prefix + "/" + leftP.name + | if rightP.instance_name == instance.name and leftP.is_input and leftP.instance_name == None: + | if leftP.name in prefix_param_dict[prefix]: + | instance_param_dict[rightP.name] = prefix_param_dict[prefix][leftP.name] + | if leftP.name + "_physical" in prefix_param_dict[prefix]: + | instance_param_dict[rightP.name+"_physical"] = prefix_param_dict[prefix][leftP.name+"_physical"] + | if rightP.instance_name == instance.name and rightP.is_input and leftP.instance_name != None: + | instance_param_dict[rightP.name + physical_string] = prefix +"/"+ leftP.instance_name + "/" + leftP.name + | if con.delay is not None: + | instance_param_dict[rightP.name + physical_string + "_delay"] = con.delay + | if leftP.instance_name == instance.name and rightP.instance_name == None and not rightP.is_input: + | # connection like r{x.port -> out} while current instance is x + | # we go through the reactors to see if parent.out is used to replace it with x.port + | name_to_search_for = prefix + "/" + rightP.name + | print(name_to_search_for) + | for key in prefix_param_dict: + | for key2 in prefix_param_dict[key]: + | if prefix_param_dict[key][key2] == name_to_search_for: + | prefix_param_dict[key][key2] = lf_federate_prefix + "/" + leftP.name + + | + | + | # launch nodes if they are federate + | to_search_feds : List[Tuple[str,Instantiation]] = [["", mainInstance]] + | nodes = [] + | while to_search_feds: + | [prefix, instance] = to_search_feds.pop(0) + | fed_prefix = prefix + "/" + instance.name + | for inst in instance.reactor.instantiations: + | to_search_feds.append((fed_prefix, inst)) + | if instance.executable != None: | nodes.append(Node(package='${fileConfig.name}', - | executable=inst[1][0], - | name=prefix+ "_" + inst[0], - | parameters=[port_params]) + | executable=instance.executable, + | name=fed_prefix.replace("/","_"), + | parameters=[prefix_param_dict[fed_prefix]]) | ) - | instances_todo.append([prefix + "_" + inst[0], inst[1][1]]) - | + | print("parameters for node with prefix " + fed_prefix, prefix_param_dict[fed_prefix]) | return LaunchDescription(nodes) """.trimMargin() @@ -229,11 +326,7 @@ class CppRos2PackageGenerator(generator: CppGenerator) { |#!/bin/bash |script_dir="$S(dirname -- "$S(readlink -f -- "${S}0")")" |source "$S{script_dir}/$relPath/src-gen/install/setup.sh" - |${ nodeGenerators.map { - "ros2 run ${fileConfig.name} ${it.nodeName} &" - }.joinLn() } - |trap "killall" EXIT SIGINT SIGTERM - |wait + |ros2 launch ${fileConfig.name} default.launch.py """.trimMargin() } } diff --git a/test/Cpp/install/.colcon_install_layout b/test/Cpp/install/.colcon_install_layout deleted file mode 100644 index 3aad5336af..0000000000 --- a/test/Cpp/install/.colcon_install_layout +++ /dev/null @@ -1 +0,0 @@ -isolated diff --git a/test/Cpp/install/COLCON_IGNORE b/test/Cpp/install/COLCON_IGNORE deleted file mode 100644 index e69de29bb2..0000000000 diff --git a/test/Cpp/install/StringConnection/share/StringConnection/package.bash b/test/Cpp/install/StringConnection/share/StringConnection/package.bash deleted file mode 100644 index cd3eff3fa7..0000000000 --- a/test/Cpp/install/StringConnection/share/StringConnection/package.bash +++ /dev/null @@ -1,39 +0,0 @@ -# generated from colcon_bash/shell/template/package.bash.em - -# This script extends the environment for this package. - -# a bash script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - # the prefix is two levels up from the package specific share directory - _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" -else - _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_bash_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source sh script of this package -_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/StringConnection/package.sh" - -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts -COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" - -# source bash hooks -_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/StringConnection/local_setup.bash" - -unset COLCON_CURRENT_PREFIX - -unset _colcon_package_bash_source_script -unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/StringConnection/share/StringConnection/package.dsv b/test/Cpp/install/StringConnection/share/StringConnection/package.dsv deleted file mode 100644 index 5885b501c1..0000000000 --- a/test/Cpp/install/StringConnection/share/StringConnection/package.dsv +++ /dev/null @@ -1,5 +0,0 @@ -source;share/StringConnection/local_setup.bash -source;share/StringConnection/local_setup.dsv -source;share/StringConnection/local_setup.ps1 -source;share/StringConnection/local_setup.sh -source;share/StringConnection/local_setup.zsh diff --git a/test/Cpp/install/StringConnection/share/StringConnection/package.ps1 b/test/Cpp/install/StringConnection/share/StringConnection/package.ps1 deleted file mode 100644 index c6c186d229..0000000000 --- a/test/Cpp/install/StringConnection/share/StringConnection/package.ps1 +++ /dev/null @@ -1,115 +0,0 @@ -# generated from colcon_powershell/shell/template/package.ps1.em - -# function to append a value to a variable -# which uses colons as separators -# duplicates as well as leading separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -function colcon_append_unique_value { - param ( - $_listname, - $_value - ) - - # get values from variable - if (Test-Path Env:$_listname) { - $_values=(Get-Item env:$_listname).Value - } else { - $_values="" - } - $_duplicate="" - # start with no values - $_all_values="" - # iterate over existing values in the variable - if ($_values) { - $_values.Split(";") | ForEach { - # not an empty string - if ($_) { - # not a duplicate of _value - if ($_ -eq $_value) { - $_duplicate="1" - } - if ($_all_values) { - $_all_values="${_all_values};$_" - } else { - $_all_values="$_" - } - } - } - } - # append only non-duplicates - if (!$_duplicate) { - # avoid leading separator - if ($_all_values) { - $_all_values="${_all_values};${_value}" - } else { - $_all_values="${_value}" - } - } - - # export the updated variable - Set-Item env:\$_listname -Value "$_all_values" -} - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -function colcon_prepend_unique_value { - param ( - $_listname, - $_value - ) - - # get values from variable - if (Test-Path Env:$_listname) { - $_values=(Get-Item env:$_listname).Value - } else { - $_values="" - } - # start with the new value - $_all_values="$_value" - # iterate over existing values in the variable - if ($_values) { - $_values.Split(";") | ForEach { - # not an empty string - if ($_) { - # not a duplicate of _value - if ($_ -ne $_value) { - # keep non-duplicate values - $_all_values="${_all_values};$_" - } - } - } - } - # export the updated variable - Set-Item env:\$_listname -Value "$_all_values" -} - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -function colcon_package_source_powershell_script { - param ( - $_colcon_package_source_powershell_script - ) - # source script with conditional trace output - if (Test-Path $_colcon_package_source_powershell_script) { - if ($env:COLCON_TRACE) { - echo ". '$_colcon_package_source_powershell_script'" - } - . "$_colcon_package_source_powershell_script" - } else { - Write-Error "not found: '$_colcon_package_source_powershell_script'" - } -} - - -# a powershell script is able to determine its own path -# the prefix is two levels up from the package specific share directory -$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName - -colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/StringConnection/local_setup.ps1" - -Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/StringConnection/share/StringConnection/package.sh b/test/Cpp/install/StringConnection/share/StringConnection/package.sh deleted file mode 100644 index e160be97a6..0000000000 --- a/test/Cpp/install/StringConnection/share/StringConnection/package.sh +++ /dev/null @@ -1,86 +0,0 @@ -# generated from colcon_core/shell/template/package.sh.em - -# This script extends the environment for this package. - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -_colcon_prepend_unique_value() { - # arguments - _listname="$1" - _value="$2" - - # get values from variable - eval _values=\"\$$_listname\" - # backup the field separator - _colcon_prepend_unique_value_IFS=$IFS - IFS=":" - # start with the new value - _all_values="$_value" - # workaround SH_WORD_SPLIT not being set in zsh - if [ "$(command -v colcon_zsh_convert_to_array)" ]; then - colcon_zsh_convert_to_array _values - fi - # iterate over existing values in the variable - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - # ignore duplicates of _value - if [ "$_item" = "$_value" ]; then - continue - fi - # keep non-duplicate values - _all_values="$_all_values:$_item" - done - unset _item - # restore the field separator - IFS=$_colcon_prepend_unique_value_IFS - unset _colcon_prepend_unique_value_IFS - # export the updated variable - eval export $_listname=\"$_all_values\" - unset _all_values - unset _values - - unset _value - unset _listname -} - -# since a plain shell script can't determine its own path when being sourced -# either use the provided COLCON_CURRENT_PREFIX -# or fall back to the build time prefix (if it exists) -_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/cs/Desktop/lingua-franca/test/Cpp/install/StringConnection" -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then - echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 - unset _colcon_package_sh_COLCON_CURRENT_PREFIX - return 1 - fi - COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" -fi -unset _colcon_package_sh_COLCON_CURRENT_PREFIX - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_sh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo "# . \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source sh hooks -_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/StringConnection/local_setup.sh" - -unset _colcon_package_sh_source_script -unset COLCON_CURRENT_PREFIX - -# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/test/Cpp/install/StringConnection/share/StringConnection/package.zsh b/test/Cpp/install/StringConnection/share/StringConnection/package.zsh deleted file mode 100644 index 3f185c2167..0000000000 --- a/test/Cpp/install/StringConnection/share/StringConnection/package.zsh +++ /dev/null @@ -1,50 +0,0 @@ -# generated from colcon_zsh/shell/template/package.zsh.em - -# This script extends the environment for this package. - -# a zsh script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - # the prefix is two levels up from the package specific share directory - _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" -else - _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_zsh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# function to convert array-like strings into arrays -# to workaround SH_WORD_SPLIT not being set -colcon_zsh_convert_to_array() { - local _listname=$1 - local _dollar="$" - local _split="{=" - local _to_array="(\"$_dollar$_split$_listname}\")" - eval $_listname=$_to_array -} - -# source sh script of this package -_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/StringConnection/package.sh" -unset convert_zsh_to_array - -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts -COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" - -# source zsh hooks -_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/StringConnection/local_setup.zsh" - -unset COLCON_CURRENT_PREFIX - -unset _colcon_package_zsh_source_script -unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/StringConnection/share/colcon-core/packages/StringConnection b/test/Cpp/install/StringConnection/share/colcon-core/packages/StringConnection deleted file mode 100644 index 97522737df..0000000000 --- a/test/Cpp/install/StringConnection/share/colcon-core/packages/StringConnection +++ /dev/null @@ -1 +0,0 @@ -ament_index_python:rclcpp:rclcpp_components:reactor-cpp-default:std_msgs \ No newline at end of file diff --git a/test/Cpp/install/VoidConnection/share/VoidConnection/package.bash b/test/Cpp/install/VoidConnection/share/VoidConnection/package.bash deleted file mode 100644 index a38e30591a..0000000000 --- a/test/Cpp/install/VoidConnection/share/VoidConnection/package.bash +++ /dev/null @@ -1,39 +0,0 @@ -# generated from colcon_bash/shell/template/package.bash.em - -# This script extends the environment for this package. - -# a bash script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - # the prefix is two levels up from the package specific share directory - _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" -else - _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_bash_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source sh script of this package -_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/VoidConnection/package.sh" - -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts -COLCON_CURRENT_PREFIX="$_colcon_package_bash_COLCON_CURRENT_PREFIX" - -# source bash hooks -_colcon_package_bash_source_script "$COLCON_CURRENT_PREFIX/share/VoidConnection/local_setup.bash" - -unset COLCON_CURRENT_PREFIX - -unset _colcon_package_bash_source_script -unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/VoidConnection/share/VoidConnection/package.dsv b/test/Cpp/install/VoidConnection/share/VoidConnection/package.dsv deleted file mode 100644 index b290715806..0000000000 --- a/test/Cpp/install/VoidConnection/share/VoidConnection/package.dsv +++ /dev/null @@ -1,5 +0,0 @@ -source;share/VoidConnection/local_setup.bash -source;share/VoidConnection/local_setup.dsv -source;share/VoidConnection/local_setup.ps1 -source;share/VoidConnection/local_setup.sh -source;share/VoidConnection/local_setup.zsh diff --git a/test/Cpp/install/VoidConnection/share/VoidConnection/package.ps1 b/test/Cpp/install/VoidConnection/share/VoidConnection/package.ps1 deleted file mode 100644 index 7583cac63a..0000000000 --- a/test/Cpp/install/VoidConnection/share/VoidConnection/package.ps1 +++ /dev/null @@ -1,115 +0,0 @@ -# generated from colcon_powershell/shell/template/package.ps1.em - -# function to append a value to a variable -# which uses colons as separators -# duplicates as well as leading separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -function colcon_append_unique_value { - param ( - $_listname, - $_value - ) - - # get values from variable - if (Test-Path Env:$_listname) { - $_values=(Get-Item env:$_listname).Value - } else { - $_values="" - } - $_duplicate="" - # start with no values - $_all_values="" - # iterate over existing values in the variable - if ($_values) { - $_values.Split(";") | ForEach { - # not an empty string - if ($_) { - # not a duplicate of _value - if ($_ -eq $_value) { - $_duplicate="1" - } - if ($_all_values) { - $_all_values="${_all_values};$_" - } else { - $_all_values="$_" - } - } - } - } - # append only non-duplicates - if (!$_duplicate) { - # avoid leading separator - if ($_all_values) { - $_all_values="${_all_values};${_value}" - } else { - $_all_values="${_value}" - } - } - - # export the updated variable - Set-Item env:\$_listname -Value "$_all_values" -} - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -function colcon_prepend_unique_value { - param ( - $_listname, - $_value - ) - - # get values from variable - if (Test-Path Env:$_listname) { - $_values=(Get-Item env:$_listname).Value - } else { - $_values="" - } - # start with the new value - $_all_values="$_value" - # iterate over existing values in the variable - if ($_values) { - $_values.Split(";") | ForEach { - # not an empty string - if ($_) { - # not a duplicate of _value - if ($_ -ne $_value) { - # keep non-duplicate values - $_all_values="${_all_values};$_" - } - } - } - } - # export the updated variable - Set-Item env:\$_listname -Value "$_all_values" -} - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -function colcon_package_source_powershell_script { - param ( - $_colcon_package_source_powershell_script - ) - # source script with conditional trace output - if (Test-Path $_colcon_package_source_powershell_script) { - if ($env:COLCON_TRACE) { - echo ". '$_colcon_package_source_powershell_script'" - } - . "$_colcon_package_source_powershell_script" - } else { - Write-Error "not found: '$_colcon_package_source_powershell_script'" - } -} - - -# a powershell script is able to determine its own path -# the prefix is two levels up from the package specific share directory -$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName - -colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/VoidConnection/local_setup.ps1" - -Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/VoidConnection/share/VoidConnection/package.sh b/test/Cpp/install/VoidConnection/share/VoidConnection/package.sh deleted file mode 100644 index 29cc2e0879..0000000000 --- a/test/Cpp/install/VoidConnection/share/VoidConnection/package.sh +++ /dev/null @@ -1,86 +0,0 @@ -# generated from colcon_core/shell/template/package.sh.em - -# This script extends the environment for this package. - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -_colcon_prepend_unique_value() { - # arguments - _listname="$1" - _value="$2" - - # get values from variable - eval _values=\"\$$_listname\" - # backup the field separator - _colcon_prepend_unique_value_IFS=$IFS - IFS=":" - # start with the new value - _all_values="$_value" - # workaround SH_WORD_SPLIT not being set in zsh - if [ "$(command -v colcon_zsh_convert_to_array)" ]; then - colcon_zsh_convert_to_array _values - fi - # iterate over existing values in the variable - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - # ignore duplicates of _value - if [ "$_item" = "$_value" ]; then - continue - fi - # keep non-duplicate values - _all_values="$_all_values:$_item" - done - unset _item - # restore the field separator - IFS=$_colcon_prepend_unique_value_IFS - unset _colcon_prepend_unique_value_IFS - # export the updated variable - eval export $_listname=\"$_all_values\" - unset _all_values - unset _values - - unset _value - unset _listname -} - -# since a plain shell script can't determine its own path when being sourced -# either use the provided COLCON_CURRENT_PREFIX -# or fall back to the build time prefix (if it exists) -_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/cs/Desktop/lingua-franca/test/Cpp/install/VoidConnection" -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then - echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 - unset _colcon_package_sh_COLCON_CURRENT_PREFIX - return 1 - fi - COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" -fi -unset _colcon_package_sh_COLCON_CURRENT_PREFIX - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_sh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo "# . \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source sh hooks -_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/VoidConnection/local_setup.sh" - -unset _colcon_package_sh_source_script -unset COLCON_CURRENT_PREFIX - -# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/test/Cpp/install/VoidConnection/share/VoidConnection/package.zsh b/test/Cpp/install/VoidConnection/share/VoidConnection/package.zsh deleted file mode 100644 index 6482d5931f..0000000000 --- a/test/Cpp/install/VoidConnection/share/VoidConnection/package.zsh +++ /dev/null @@ -1,50 +0,0 @@ -# generated from colcon_zsh/shell/template/package.zsh.em - -# This script extends the environment for this package. - -# a zsh script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - # the prefix is two levels up from the package specific share directory - _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" -else - _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_zsh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# function to convert array-like strings into arrays -# to workaround SH_WORD_SPLIT not being set -colcon_zsh_convert_to_array() { - local _listname=$1 - local _dollar="$" - local _split="{=" - local _to_array="(\"$_dollar$_split$_listname}\")" - eval $_listname=$_to_array -} - -# source sh script of this package -_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/VoidConnection/package.sh" -unset convert_zsh_to_array - -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced scripts -COLCON_CURRENT_PREFIX="$_colcon_package_zsh_COLCON_CURRENT_PREFIX" - -# source zsh hooks -_colcon_package_zsh_source_script "$COLCON_CURRENT_PREFIX/share/VoidConnection/local_setup.zsh" - -unset COLCON_CURRENT_PREFIX - -unset _colcon_package_zsh_source_script -unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/VoidConnection/share/colcon-core/packages/VoidConnection b/test/Cpp/install/VoidConnection/share/colcon-core/packages/VoidConnection deleted file mode 100644 index 97522737df..0000000000 --- a/test/Cpp/install/VoidConnection/share/colcon-core/packages/VoidConnection +++ /dev/null @@ -1 +0,0 @@ -ament_index_python:rclcpp:rclcpp_components:reactor-cpp-default:std_msgs \ No newline at end of file diff --git a/test/Cpp/install/_local_setup_util_ps1.py b/test/Cpp/install/_local_setup_util_ps1.py deleted file mode 100644 index 98348eebcf..0000000000 --- a/test/Cpp/install/_local_setup_util_ps1.py +++ /dev/null @@ -1,404 +0,0 @@ -# Copyright 2016-2019 Dirk Thomas -# Licensed under the Apache License, Version 2.0 - -import argparse -from collections import OrderedDict -import os -from pathlib import Path -import sys - - -FORMAT_STR_COMMENT_LINE = '# {comment}' -FORMAT_STR_SET_ENV_VAR = 'Set-Item -Path "Env:{name}" -Value "{value}"' -FORMAT_STR_USE_ENV_VAR = '$env:{name}' -FORMAT_STR_INVOKE_SCRIPT = '_colcon_prefix_powershell_source_script "{script_path}"' -FORMAT_STR_REMOVE_LEADING_SEPARATOR = '' -FORMAT_STR_REMOVE_TRAILING_SEPARATOR = '' - -DSV_TYPE_APPEND_NON_DUPLICATE = 'append-non-duplicate' -DSV_TYPE_PREPEND_NON_DUPLICATE = 'prepend-non-duplicate' -DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS = 'prepend-non-duplicate-if-exists' -DSV_TYPE_SET = 'set' -DSV_TYPE_SET_IF_UNSET = 'set-if-unset' -DSV_TYPE_SOURCE = 'source' - - -def main(argv=sys.argv[1:]): # noqa: D103 - parser = argparse.ArgumentParser( - description='Output shell commands for the packages in topological ' - 'order') - parser.add_argument( - 'primary_extension', - help='The file extension of the primary shell') - parser.add_argument( - 'additional_extension', nargs='?', - help='The additional file extension to be considered') - parser.add_argument( - '--merged-install', action='store_true', - help='All install prefixes are merged into a single location') - args = parser.parse_args(argv) - - packages = get_packages(Path(__file__).parent, args.merged_install) - - ordered_packages = order_packages(packages) - for pkg_name in ordered_packages: - if _include_comments(): - print( - FORMAT_STR_COMMENT_LINE.format_map( - {'comment': 'Package: ' + pkg_name})) - prefix = os.path.abspath(os.path.dirname(__file__)) - if not args.merged_install: - prefix = os.path.join(prefix, pkg_name) - for line in get_commands( - pkg_name, prefix, args.primary_extension, - args.additional_extension - ): - print(line) - - for line in _remove_ending_separators(): - print(line) - - -def get_packages(prefix_path, merged_install): - """ - Find packages based on colcon-specific files created during installation. - - :param Path prefix_path: The install prefix path of all packages - :param bool merged_install: The flag if the packages are all installed - directly in the prefix or if each package is installed in a subdirectory - named after the package - :returns: A mapping from the package name to the set of runtime - dependencies - :rtype: dict - """ - packages = {} - # since importing colcon_core isn't feasible here the following constant - # must match colcon_core.location.get_relative_package_index_path() - subdirectory = 'share/colcon-core/packages' - if merged_install: - # return if workspace is empty - if not (prefix_path / subdirectory).is_dir(): - return packages - # find all files in the subdirectory - for p in (prefix_path / subdirectory).iterdir(): - if not p.is_file(): - continue - if p.name.startswith('.'): - continue - add_package_runtime_dependencies(p, packages) - else: - # for each subdirectory look for the package specific file - for p in prefix_path.iterdir(): - if not p.is_dir(): - continue - if p.name.startswith('.'): - continue - p = p / subdirectory / p.name - if p.is_file(): - add_package_runtime_dependencies(p, packages) - - # remove unknown dependencies - pkg_names = set(packages.keys()) - for k in packages.keys(): - packages[k] = {d for d in packages[k] if d in pkg_names} - - return packages - - -def add_package_runtime_dependencies(path, packages): - """ - Check the path and if it exists extract the packages runtime dependencies. - - :param Path path: The resource file containing the runtime dependencies - :param dict packages: A mapping from package names to the sets of runtime - dependencies to add to - """ - content = path.read_text() - dependencies = set(content.split(os.pathsep) if content else []) - packages[path.name] = dependencies - - -def order_packages(packages): - """ - Order packages topologically. - - :param dict packages: A mapping from package name to the set of runtime - dependencies - :returns: The package names - :rtype: list - """ - # select packages with no dependencies in alphabetical order - to_be_ordered = list(packages.keys()) - ordered = [] - while to_be_ordered: - pkg_names_without_deps = [ - name for name in to_be_ordered if not packages[name]] - if not pkg_names_without_deps: - reduce_cycle_set(packages) - raise RuntimeError( - 'Circular dependency between: ' + ', '.join(sorted(packages))) - pkg_names_without_deps.sort() - pkg_name = pkg_names_without_deps[0] - to_be_ordered.remove(pkg_name) - ordered.append(pkg_name) - # remove item from dependency lists - for k in list(packages.keys()): - if pkg_name in packages[k]: - packages[k].remove(pkg_name) - return ordered - - -def reduce_cycle_set(packages): - """ - Reduce the set of packages to the ones part of the circular dependency. - - :param dict packages: A mapping from package name to the set of runtime - dependencies which is modified in place - """ - last_depended = None - while len(packages) > 0: - # get all remaining dependencies - depended = set() - for pkg_name, dependencies in packages.items(): - depended = depended.union(dependencies) - # remove all packages which are not dependent on - for name in list(packages.keys()): - if name not in depended: - del packages[name] - if last_depended: - # if remaining packages haven't changed return them - if last_depended == depended: - return packages.keys() - # otherwise reduce again - last_depended = depended - - -def _include_comments(): - # skipping comment lines when COLCON_TRACE is not set speeds up the - # processing especially on Windows - return bool(os.environ.get('COLCON_TRACE')) - - -def get_commands(pkg_name, prefix, primary_extension, additional_extension): - commands = [] - package_dsv_path = os.path.join(prefix, 'share', pkg_name, 'package.dsv') - if os.path.exists(package_dsv_path): - commands += process_dsv_file( - package_dsv_path, prefix, primary_extension, additional_extension) - return commands - - -def process_dsv_file( - dsv_path, prefix, primary_extension=None, additional_extension=None -): - commands = [] - if _include_comments(): - commands.append(FORMAT_STR_COMMENT_LINE.format_map({'comment': dsv_path})) - with open(dsv_path, 'r') as h: - content = h.read() - lines = content.splitlines() - - basenames = OrderedDict() - for i, line in enumerate(lines): - # skip over empty or whitespace-only lines - if not line.strip(): - continue - try: - type_, remainder = line.split(';', 1) - except ValueError: - raise RuntimeError( - "Line %d in '%s' doesn't contain a semicolon separating the " - 'type from the arguments' % (i + 1, dsv_path)) - if type_ != DSV_TYPE_SOURCE: - # handle non-source lines - try: - commands += handle_dsv_types_except_source( - type_, remainder, prefix) - except RuntimeError as e: - raise RuntimeError( - "Line %d in '%s' %s" % (i + 1, dsv_path, e)) from e - else: - # group remaining source lines by basename - path_without_ext, ext = os.path.splitext(remainder) - if path_without_ext not in basenames: - basenames[path_without_ext] = set() - assert ext.startswith('.') - ext = ext[1:] - if ext in (primary_extension, additional_extension): - basenames[path_without_ext].add(ext) - - # add the dsv extension to each basename if the file exists - for basename, extensions in basenames.items(): - if not os.path.isabs(basename): - basename = os.path.join(prefix, basename) - if os.path.exists(basename + '.dsv'): - extensions.add('dsv') - - for basename, extensions in basenames.items(): - if not os.path.isabs(basename): - basename = os.path.join(prefix, basename) - if 'dsv' in extensions: - # process dsv files recursively - commands += process_dsv_file( - basename + '.dsv', prefix, primary_extension=primary_extension, - additional_extension=additional_extension) - elif primary_extension in extensions and len(extensions) == 1: - # source primary-only files - commands += [ - FORMAT_STR_INVOKE_SCRIPT.format_map({ - 'prefix': prefix, - 'script_path': basename + '.' + primary_extension})] - elif additional_extension in extensions: - # source non-primary files - commands += [ - FORMAT_STR_INVOKE_SCRIPT.format_map({ - 'prefix': prefix, - 'script_path': basename + '.' + additional_extension})] - - return commands - - -def handle_dsv_types_except_source(type_, remainder, prefix): - commands = [] - if type_ in (DSV_TYPE_SET, DSV_TYPE_SET_IF_UNSET): - try: - env_name, value = remainder.split(';', 1) - except ValueError: - raise RuntimeError( - "doesn't contain a semicolon separating the environment name " - 'from the value') - try_prefixed_value = os.path.join(prefix, value) if value else prefix - if os.path.exists(try_prefixed_value): - value = try_prefixed_value - if type_ == DSV_TYPE_SET: - commands += _set(env_name, value) - elif type_ == DSV_TYPE_SET_IF_UNSET: - commands += _set_if_unset(env_name, value) - else: - assert False - elif type_ in ( - DSV_TYPE_APPEND_NON_DUPLICATE, - DSV_TYPE_PREPEND_NON_DUPLICATE, - DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS - ): - try: - env_name_and_values = remainder.split(';') - except ValueError: - raise RuntimeError( - "doesn't contain a semicolon separating the environment name " - 'from the values') - env_name = env_name_and_values[0] - values = env_name_and_values[1:] - for value in values: - if not value: - value = prefix - elif not os.path.isabs(value): - value = os.path.join(prefix, value) - if ( - type_ == DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS and - not os.path.exists(value) - ): - comment = f'skip extending {env_name} with not existing ' \ - f'path: {value}' - if _include_comments(): - commands.append( - FORMAT_STR_COMMENT_LINE.format_map({'comment': comment})) - elif type_ == DSV_TYPE_APPEND_NON_DUPLICATE: - commands += _append_unique_value(env_name, value) - else: - commands += _prepend_unique_value(env_name, value) - else: - raise RuntimeError( - 'contains an unknown environment hook type: ' + type_) - return commands - - -env_state = {} - - -def _append_unique_value(name, value): - global env_state - if name not in env_state: - if os.environ.get(name): - env_state[name] = set(os.environ[name].split(os.pathsep)) - else: - env_state[name] = set() - # append even if the variable has not been set yet, in case a shell script sets the - # same variable without the knowledge of this Python script. - # later _remove_ending_separators() will cleanup any unintentional leading separator - extend = FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + os.pathsep - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': extend + value}) - if value not in env_state[name]: - env_state[name].add(value) - else: - if not _include_comments(): - return [] - line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) - return [line] - - -def _prepend_unique_value(name, value): - global env_state - if name not in env_state: - if os.environ.get(name): - env_state[name] = set(os.environ[name].split(os.pathsep)) - else: - env_state[name] = set() - # prepend even if the variable has not been set yet, in case a shell script sets the - # same variable without the knowledge of this Python script. - # later _remove_ending_separators() will cleanup any unintentional trailing separator - extend = os.pathsep + FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': value + extend}) - if value not in env_state[name]: - env_state[name].add(value) - else: - if not _include_comments(): - return [] - line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) - return [line] - - -# generate commands for removing prepended underscores -def _remove_ending_separators(): - # do nothing if the shell extension does not implement the logic - if FORMAT_STR_REMOVE_TRAILING_SEPARATOR is None: - return [] - - global env_state - commands = [] - for name in env_state: - # skip variables that already had values before this script started prepending - if name in os.environ: - continue - commands += [ - FORMAT_STR_REMOVE_LEADING_SEPARATOR.format_map({'name': name}), - FORMAT_STR_REMOVE_TRAILING_SEPARATOR.format_map({'name': name})] - return commands - - -def _set(name, value): - global env_state - env_state[name] = value - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': value}) - return [line] - - -def _set_if_unset(name, value): - global env_state - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': value}) - if env_state.get(name, os.environ.get(name)): - line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) - return [line] - - -if __name__ == '__main__': # pragma: no cover - try: - rc = main() - except RuntimeError as e: - print(str(e), file=sys.stderr) - rc = 1 - sys.exit(rc) diff --git a/test/Cpp/install/_local_setup_util_sh.py b/test/Cpp/install/_local_setup_util_sh.py deleted file mode 100644 index 35c017b255..0000000000 --- a/test/Cpp/install/_local_setup_util_sh.py +++ /dev/null @@ -1,404 +0,0 @@ -# Copyright 2016-2019 Dirk Thomas -# Licensed under the Apache License, Version 2.0 - -import argparse -from collections import OrderedDict -import os -from pathlib import Path -import sys - - -FORMAT_STR_COMMENT_LINE = '# {comment}' -FORMAT_STR_SET_ENV_VAR = 'export {name}="{value}"' -FORMAT_STR_USE_ENV_VAR = '${name}' -FORMAT_STR_INVOKE_SCRIPT = 'COLCON_CURRENT_PREFIX="{prefix}" _colcon_prefix_sh_source_script "{script_path}"' -FORMAT_STR_REMOVE_LEADING_SEPARATOR = 'if [ "$(echo -n ${name} | head -c 1)" = ":" ]; then export {name}=${{{name}#?}} ; fi' -FORMAT_STR_REMOVE_TRAILING_SEPARATOR = 'if [ "$(echo -n ${name} | tail -c 1)" = ":" ]; then export {name}=${{{name}%?}} ; fi' - -DSV_TYPE_APPEND_NON_DUPLICATE = 'append-non-duplicate' -DSV_TYPE_PREPEND_NON_DUPLICATE = 'prepend-non-duplicate' -DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS = 'prepend-non-duplicate-if-exists' -DSV_TYPE_SET = 'set' -DSV_TYPE_SET_IF_UNSET = 'set-if-unset' -DSV_TYPE_SOURCE = 'source' - - -def main(argv=sys.argv[1:]): # noqa: D103 - parser = argparse.ArgumentParser( - description='Output shell commands for the packages in topological ' - 'order') - parser.add_argument( - 'primary_extension', - help='The file extension of the primary shell') - parser.add_argument( - 'additional_extension', nargs='?', - help='The additional file extension to be considered') - parser.add_argument( - '--merged-install', action='store_true', - help='All install prefixes are merged into a single location') - args = parser.parse_args(argv) - - packages = get_packages(Path(__file__).parent, args.merged_install) - - ordered_packages = order_packages(packages) - for pkg_name in ordered_packages: - if _include_comments(): - print( - FORMAT_STR_COMMENT_LINE.format_map( - {'comment': 'Package: ' + pkg_name})) - prefix = os.path.abspath(os.path.dirname(__file__)) - if not args.merged_install: - prefix = os.path.join(prefix, pkg_name) - for line in get_commands( - pkg_name, prefix, args.primary_extension, - args.additional_extension - ): - print(line) - - for line in _remove_ending_separators(): - print(line) - - -def get_packages(prefix_path, merged_install): - """ - Find packages based on colcon-specific files created during installation. - - :param Path prefix_path: The install prefix path of all packages - :param bool merged_install: The flag if the packages are all installed - directly in the prefix or if each package is installed in a subdirectory - named after the package - :returns: A mapping from the package name to the set of runtime - dependencies - :rtype: dict - """ - packages = {} - # since importing colcon_core isn't feasible here the following constant - # must match colcon_core.location.get_relative_package_index_path() - subdirectory = 'share/colcon-core/packages' - if merged_install: - # return if workspace is empty - if not (prefix_path / subdirectory).is_dir(): - return packages - # find all files in the subdirectory - for p in (prefix_path / subdirectory).iterdir(): - if not p.is_file(): - continue - if p.name.startswith('.'): - continue - add_package_runtime_dependencies(p, packages) - else: - # for each subdirectory look for the package specific file - for p in prefix_path.iterdir(): - if not p.is_dir(): - continue - if p.name.startswith('.'): - continue - p = p / subdirectory / p.name - if p.is_file(): - add_package_runtime_dependencies(p, packages) - - # remove unknown dependencies - pkg_names = set(packages.keys()) - for k in packages.keys(): - packages[k] = {d for d in packages[k] if d in pkg_names} - - return packages - - -def add_package_runtime_dependencies(path, packages): - """ - Check the path and if it exists extract the packages runtime dependencies. - - :param Path path: The resource file containing the runtime dependencies - :param dict packages: A mapping from package names to the sets of runtime - dependencies to add to - """ - content = path.read_text() - dependencies = set(content.split(os.pathsep) if content else []) - packages[path.name] = dependencies - - -def order_packages(packages): - """ - Order packages topologically. - - :param dict packages: A mapping from package name to the set of runtime - dependencies - :returns: The package names - :rtype: list - """ - # select packages with no dependencies in alphabetical order - to_be_ordered = list(packages.keys()) - ordered = [] - while to_be_ordered: - pkg_names_without_deps = [ - name for name in to_be_ordered if not packages[name]] - if not pkg_names_without_deps: - reduce_cycle_set(packages) - raise RuntimeError( - 'Circular dependency between: ' + ', '.join(sorted(packages))) - pkg_names_without_deps.sort() - pkg_name = pkg_names_without_deps[0] - to_be_ordered.remove(pkg_name) - ordered.append(pkg_name) - # remove item from dependency lists - for k in list(packages.keys()): - if pkg_name in packages[k]: - packages[k].remove(pkg_name) - return ordered - - -def reduce_cycle_set(packages): - """ - Reduce the set of packages to the ones part of the circular dependency. - - :param dict packages: A mapping from package name to the set of runtime - dependencies which is modified in place - """ - last_depended = None - while len(packages) > 0: - # get all remaining dependencies - depended = set() - for pkg_name, dependencies in packages.items(): - depended = depended.union(dependencies) - # remove all packages which are not dependent on - for name in list(packages.keys()): - if name not in depended: - del packages[name] - if last_depended: - # if remaining packages haven't changed return them - if last_depended == depended: - return packages.keys() - # otherwise reduce again - last_depended = depended - - -def _include_comments(): - # skipping comment lines when COLCON_TRACE is not set speeds up the - # processing especially on Windows - return bool(os.environ.get('COLCON_TRACE')) - - -def get_commands(pkg_name, prefix, primary_extension, additional_extension): - commands = [] - package_dsv_path = os.path.join(prefix, 'share', pkg_name, 'package.dsv') - if os.path.exists(package_dsv_path): - commands += process_dsv_file( - package_dsv_path, prefix, primary_extension, additional_extension) - return commands - - -def process_dsv_file( - dsv_path, prefix, primary_extension=None, additional_extension=None -): - commands = [] - if _include_comments(): - commands.append(FORMAT_STR_COMMENT_LINE.format_map({'comment': dsv_path})) - with open(dsv_path, 'r') as h: - content = h.read() - lines = content.splitlines() - - basenames = OrderedDict() - for i, line in enumerate(lines): - # skip over empty or whitespace-only lines - if not line.strip(): - continue - try: - type_, remainder = line.split(';', 1) - except ValueError: - raise RuntimeError( - "Line %d in '%s' doesn't contain a semicolon separating the " - 'type from the arguments' % (i + 1, dsv_path)) - if type_ != DSV_TYPE_SOURCE: - # handle non-source lines - try: - commands += handle_dsv_types_except_source( - type_, remainder, prefix) - except RuntimeError as e: - raise RuntimeError( - "Line %d in '%s' %s" % (i + 1, dsv_path, e)) from e - else: - # group remaining source lines by basename - path_without_ext, ext = os.path.splitext(remainder) - if path_without_ext not in basenames: - basenames[path_without_ext] = set() - assert ext.startswith('.') - ext = ext[1:] - if ext in (primary_extension, additional_extension): - basenames[path_without_ext].add(ext) - - # add the dsv extension to each basename if the file exists - for basename, extensions in basenames.items(): - if not os.path.isabs(basename): - basename = os.path.join(prefix, basename) - if os.path.exists(basename + '.dsv'): - extensions.add('dsv') - - for basename, extensions in basenames.items(): - if not os.path.isabs(basename): - basename = os.path.join(prefix, basename) - if 'dsv' in extensions: - # process dsv files recursively - commands += process_dsv_file( - basename + '.dsv', prefix, primary_extension=primary_extension, - additional_extension=additional_extension) - elif primary_extension in extensions and len(extensions) == 1: - # source primary-only files - commands += [ - FORMAT_STR_INVOKE_SCRIPT.format_map({ - 'prefix': prefix, - 'script_path': basename + '.' + primary_extension})] - elif additional_extension in extensions: - # source non-primary files - commands += [ - FORMAT_STR_INVOKE_SCRIPT.format_map({ - 'prefix': prefix, - 'script_path': basename + '.' + additional_extension})] - - return commands - - -def handle_dsv_types_except_source(type_, remainder, prefix): - commands = [] - if type_ in (DSV_TYPE_SET, DSV_TYPE_SET_IF_UNSET): - try: - env_name, value = remainder.split(';', 1) - except ValueError: - raise RuntimeError( - "doesn't contain a semicolon separating the environment name " - 'from the value') - try_prefixed_value = os.path.join(prefix, value) if value else prefix - if os.path.exists(try_prefixed_value): - value = try_prefixed_value - if type_ == DSV_TYPE_SET: - commands += _set(env_name, value) - elif type_ == DSV_TYPE_SET_IF_UNSET: - commands += _set_if_unset(env_name, value) - else: - assert False - elif type_ in ( - DSV_TYPE_APPEND_NON_DUPLICATE, - DSV_TYPE_PREPEND_NON_DUPLICATE, - DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS - ): - try: - env_name_and_values = remainder.split(';') - except ValueError: - raise RuntimeError( - "doesn't contain a semicolon separating the environment name " - 'from the values') - env_name = env_name_and_values[0] - values = env_name_and_values[1:] - for value in values: - if not value: - value = prefix - elif not os.path.isabs(value): - value = os.path.join(prefix, value) - if ( - type_ == DSV_TYPE_PREPEND_NON_DUPLICATE_IF_EXISTS and - not os.path.exists(value) - ): - comment = f'skip extending {env_name} with not existing ' \ - f'path: {value}' - if _include_comments(): - commands.append( - FORMAT_STR_COMMENT_LINE.format_map({'comment': comment})) - elif type_ == DSV_TYPE_APPEND_NON_DUPLICATE: - commands += _append_unique_value(env_name, value) - else: - commands += _prepend_unique_value(env_name, value) - else: - raise RuntimeError( - 'contains an unknown environment hook type: ' + type_) - return commands - - -env_state = {} - - -def _append_unique_value(name, value): - global env_state - if name not in env_state: - if os.environ.get(name): - env_state[name] = set(os.environ[name].split(os.pathsep)) - else: - env_state[name] = set() - # append even if the variable has not been set yet, in case a shell script sets the - # same variable without the knowledge of this Python script. - # later _remove_ending_separators() will cleanup any unintentional leading separator - extend = FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) + os.pathsep - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': extend + value}) - if value not in env_state[name]: - env_state[name].add(value) - else: - if not _include_comments(): - return [] - line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) - return [line] - - -def _prepend_unique_value(name, value): - global env_state - if name not in env_state: - if os.environ.get(name): - env_state[name] = set(os.environ[name].split(os.pathsep)) - else: - env_state[name] = set() - # prepend even if the variable has not been set yet, in case a shell script sets the - # same variable without the knowledge of this Python script. - # later _remove_ending_separators() will cleanup any unintentional trailing separator - extend = os.pathsep + FORMAT_STR_USE_ENV_VAR.format_map({'name': name}) - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': value + extend}) - if value not in env_state[name]: - env_state[name].add(value) - else: - if not _include_comments(): - return [] - line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) - return [line] - - -# generate commands for removing prepended underscores -def _remove_ending_separators(): - # do nothing if the shell extension does not implement the logic - if FORMAT_STR_REMOVE_TRAILING_SEPARATOR is None: - return [] - - global env_state - commands = [] - for name in env_state: - # skip variables that already had values before this script started prepending - if name in os.environ: - continue - commands += [ - FORMAT_STR_REMOVE_LEADING_SEPARATOR.format_map({'name': name}), - FORMAT_STR_REMOVE_TRAILING_SEPARATOR.format_map({'name': name})] - return commands - - -def _set(name, value): - global env_state - env_state[name] = value - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': value}) - return [line] - - -def _set_if_unset(name, value): - global env_state - line = FORMAT_STR_SET_ENV_VAR.format_map( - {'name': name, 'value': value}) - if env_state.get(name, os.environ.get(name)): - line = FORMAT_STR_COMMENT_LINE.format_map({'comment': line}) - return [line] - - -if __name__ == '__main__': # pragma: no cover - try: - rc = main() - except RuntimeError as e: - print(str(e), file=sys.stderr) - rc = 1 - sys.exit(rc) diff --git a/test/Cpp/install/local_setup.bash b/test/Cpp/install/local_setup.bash deleted file mode 100644 index efd5f8c9e2..0000000000 --- a/test/Cpp/install/local_setup.bash +++ /dev/null @@ -1,107 +0,0 @@ -# generated from colcon_bash/shell/template/prefix.bash.em - -# This script extends the environment with all packages contained in this -# prefix path. - -# a bash script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - _colcon_prefix_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" > /dev/null && pwd)" -else - _colcon_prefix_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -_colcon_prefix_bash_prepend_unique_value() { - # arguments - _listname="$1" - _value="$2" - - # get values from variable - eval _values=\"\$$_listname\" - # backup the field separator - _colcon_prefix_bash_prepend_unique_value_IFS="$IFS" - IFS=":" - # start with the new value - _all_values="$_value" - # iterate over existing values in the variable - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - # ignore duplicates of _value - if [ "$_item" = "$_value" ]; then - continue - fi - # keep non-duplicate values - _all_values="$_all_values:$_item" - done - unset _item - # restore the field separator - IFS="$_colcon_prefix_bash_prepend_unique_value_IFS" - unset _colcon_prefix_bash_prepend_unique_value_IFS - # export the updated variable - eval export $_listname=\"$_all_values\" - unset _all_values - unset _values - - unset _value - unset _listname -} - -# add this prefix to the COLCON_PREFIX_PATH -_colcon_prefix_bash_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_bash_COLCON_CURRENT_PREFIX" -unset _colcon_prefix_bash_prepend_unique_value - -# check environment variable for custom Python executable -if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then - if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then - echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" - return 1 - fi - _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" -else - # try the Python executable known at configure time - _colcon_python_executable="/usr/bin/python3" - # if it doesn't exist try a fall back - if [ ! -f "$_colcon_python_executable" ]; then - if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then - echo "error: unable to find python3 executable" - return 1 - fi - _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` - fi -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -_colcon_prefix_sh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$1" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# get all commands in topological order -_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_bash_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh bash)" -unset _colcon_python_executable -if [ -n "$COLCON_TRACE" ]; then - echo "Execute generated script:" - echo "<<<" - echo "${_colcon_ordered_commands}" - echo ">>>" -fi -eval "${_colcon_ordered_commands}" -unset _colcon_ordered_commands - -unset _colcon_prefix_sh_source_script - -unset _colcon_prefix_bash_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/local_setup.ps1 b/test/Cpp/install/local_setup.ps1 deleted file mode 100644 index 6f68c8dede..0000000000 --- a/test/Cpp/install/local_setup.ps1 +++ /dev/null @@ -1,55 +0,0 @@ -# generated from colcon_powershell/shell/template/prefix.ps1.em - -# This script extends the environment with all packages contained in this -# prefix path. - -# check environment variable for custom Python executable -if ($env:COLCON_PYTHON_EXECUTABLE) { - if (!(Test-Path "$env:COLCON_PYTHON_EXECUTABLE" -PathType Leaf)) { - echo "error: COLCON_PYTHON_EXECUTABLE '$env:COLCON_PYTHON_EXECUTABLE' doesn't exist" - exit 1 - } - $_colcon_python_executable="$env:COLCON_PYTHON_EXECUTABLE" -} else { - # use the Python executable known at configure time - $_colcon_python_executable="/usr/bin/python3" - # if it doesn't exist try a fall back - if (!(Test-Path "$_colcon_python_executable" -PathType Leaf)) { - if (!(Get-Command "python3" -ErrorAction SilentlyContinue)) { - echo "error: unable to find python3 executable" - exit 1 - } - $_colcon_python_executable="python3" - } -} - -# function to source another script with conditional trace output -# first argument: the path of the script -function _colcon_prefix_powershell_source_script { - param ( - $_colcon_prefix_powershell_source_script_param - ) - # source script with conditional trace output - if (Test-Path $_colcon_prefix_powershell_source_script_param) { - if ($env:COLCON_TRACE) { - echo ". '$_colcon_prefix_powershell_source_script_param'" - } - . "$_colcon_prefix_powershell_source_script_param" - } else { - Write-Error "not found: '$_colcon_prefix_powershell_source_script_param'" - } -} - -# get all commands in topological order -$_colcon_ordered_commands = & "$_colcon_python_executable" "$(Split-Path $PSCommandPath -Parent)/_local_setup_util_ps1.py" ps1 - -# execute all commands in topological order -if ($env:COLCON_TRACE) { - echo "Execute generated script:" - echo "<<<" - $_colcon_ordered_commands.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) | Write-Output - echo ">>>" -} -if ($_colcon_ordered_commands) { - $_colcon_ordered_commands.Split([Environment]::NewLine, [StringSplitOptions]::RemoveEmptyEntries) | Invoke-Expression -} diff --git a/test/Cpp/install/local_setup.sh b/test/Cpp/install/local_setup.sh deleted file mode 100644 index 58097ae5c7..0000000000 --- a/test/Cpp/install/local_setup.sh +++ /dev/null @@ -1,137 +0,0 @@ -# generated from colcon_core/shell/template/prefix.sh.em - -# This script extends the environment with all packages contained in this -# prefix path. - -# since a plain shell script can't determine its own path when being sourced -# either use the provided COLCON_CURRENT_PREFIX -# or fall back to the build time prefix (if it exists) -_colcon_prefix_sh_COLCON_CURRENT_PREFIX="/home/cs/Desktop/lingua-franca/test/Cpp/install" -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - if [ ! -d "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX" ]; then - echo "The build time path \"$_colcon_prefix_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 - unset _colcon_prefix_sh_COLCON_CURRENT_PREFIX - return 1 - fi -else - _colcon_prefix_sh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -_colcon_prefix_sh_prepend_unique_value() { - # arguments - _listname="$1" - _value="$2" - - # get values from variable - eval _values=\"\$$_listname\" - # backup the field separator - _colcon_prefix_sh_prepend_unique_value_IFS="$IFS" - IFS=":" - # start with the new value - _all_values="$_value" - _contained_value="" - # iterate over existing values in the variable - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - # ignore duplicates of _value - if [ "$_item" = "$_value" ]; then - _contained_value=1 - continue - fi - # keep non-duplicate values - _all_values="$_all_values:$_item" - done - unset _item - if [ -z "$_contained_value" ]; then - if [ -n "$COLCON_TRACE" ]; then - if [ "$_all_values" = "$_value" ]; then - echo "export $_listname=$_value" - else - echo "export $_listname=$_value:\$$_listname" - fi - fi - fi - unset _contained_value - # restore the field separator - IFS="$_colcon_prefix_sh_prepend_unique_value_IFS" - unset _colcon_prefix_sh_prepend_unique_value_IFS - # export the updated variable - eval export $_listname=\"$_all_values\" - unset _all_values - unset _values - - unset _value - unset _listname -} - -# add this prefix to the COLCON_PREFIX_PATH -_colcon_prefix_sh_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX" -unset _colcon_prefix_sh_prepend_unique_value - -# check environment variable for custom Python executable -if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then - if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then - echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" - return 1 - fi - _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" -else - # try the Python executable known at configure time - _colcon_python_executable="/usr/bin/python3" - # if it doesn't exist try a fall back - if [ ! -f "$_colcon_python_executable" ]; then - if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then - echo "error: unable to find python3 executable" - return 1 - fi - _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` - fi -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -_colcon_prefix_sh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo "# . \"$1\"" - fi - . "$1" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# get all commands in topological order -_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_sh_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh)" -unset _colcon_python_executable -if [ -n "$COLCON_TRACE" ]; then - echo "_colcon_prefix_sh_source_script() { - if [ -f \"\$1\" ]; then - if [ -n \"\$COLCON_TRACE\" ]; then - echo \"# . \\\"\$1\\\"\" - fi - . \"\$1\" - else - echo \"not found: \\\"\$1\\\"\" 1>&2 - fi - }" - echo "# Execute generated script:" - echo "# <<<" - echo "${_colcon_ordered_commands}" - echo "# >>>" - echo "unset _colcon_prefix_sh_source_script" -fi -eval "${_colcon_ordered_commands}" -unset _colcon_ordered_commands - -unset _colcon_prefix_sh_source_script - -unset _colcon_prefix_sh_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/local_setup.zsh b/test/Cpp/install/local_setup.zsh deleted file mode 100644 index f7a8d904f2..0000000000 --- a/test/Cpp/install/local_setup.zsh +++ /dev/null @@ -1,120 +0,0 @@ -# generated from colcon_zsh/shell/template/prefix.zsh.em - -# This script extends the environment with all packages contained in this -# prefix path. - -# a zsh script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - _colcon_prefix_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd)" -else - _colcon_prefix_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to convert array-like strings into arrays -# to workaround SH_WORD_SPLIT not being set -_colcon_prefix_zsh_convert_to_array() { - local _listname=$1 - local _dollar="$" - local _split="{=" - local _to_array="(\"$_dollar$_split$_listname}\")" - eval $_listname=$_to_array -} - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -_colcon_prefix_zsh_prepend_unique_value() { - # arguments - _listname="$1" - _value="$2" - - # get values from variable - eval _values=\"\$$_listname\" - # backup the field separator - _colcon_prefix_zsh_prepend_unique_value_IFS="$IFS" - IFS=":" - # start with the new value - _all_values="$_value" - # workaround SH_WORD_SPLIT not being set - _colcon_prefix_zsh_convert_to_array _values - # iterate over existing values in the variable - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - # ignore duplicates of _value - if [ "$_item" = "$_value" ]; then - continue - fi - # keep non-duplicate values - _all_values="$_all_values:$_item" - done - unset _item - # restore the field separator - IFS="$_colcon_prefix_zsh_prepend_unique_value_IFS" - unset _colcon_prefix_zsh_prepend_unique_value_IFS - # export the updated variable - eval export $_listname=\"$_all_values\" - unset _all_values - unset _values - - unset _value - unset _listname -} - -# add this prefix to the COLCON_PREFIX_PATH -_colcon_prefix_zsh_prepend_unique_value COLCON_PREFIX_PATH "$_colcon_prefix_zsh_COLCON_CURRENT_PREFIX" -unset _colcon_prefix_zsh_prepend_unique_value -unset _colcon_prefix_zsh_convert_to_array - -# check environment variable for custom Python executable -if [ -n "$COLCON_PYTHON_EXECUTABLE" ]; then - if [ ! -f "$COLCON_PYTHON_EXECUTABLE" ]; then - echo "error: COLCON_PYTHON_EXECUTABLE '$COLCON_PYTHON_EXECUTABLE' doesn't exist" - return 1 - fi - _colcon_python_executable="$COLCON_PYTHON_EXECUTABLE" -else - # try the Python executable known at configure time - _colcon_python_executable="/usr/bin/python3" - # if it doesn't exist try a fall back - if [ ! -f "$_colcon_python_executable" ]; then - if ! /usr/bin/env python3 --version > /dev/null 2> /dev/null; then - echo "error: unable to find python3 executable" - return 1 - fi - _colcon_python_executable=`/usr/bin/env python3 -c "import sys; print(sys.executable)"` - fi -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -_colcon_prefix_sh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$1" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# get all commands in topological order -_colcon_ordered_commands="$($_colcon_python_executable "$_colcon_prefix_zsh_COLCON_CURRENT_PREFIX/_local_setup_util_sh.py" sh zsh)" -unset _colcon_python_executable -if [ -n "$COLCON_TRACE" ]; then - echo "Execute generated script:" - echo "<<<" - echo "${_colcon_ordered_commands}" - echo ">>>" -fi -eval "${_colcon_ordered_commands}" -unset _colcon_ordered_commands - -unset _colcon_prefix_sh_source_script - -unset _colcon_prefix_zsh_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/action.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/action.hh deleted file mode 100644 index ac26aec6b6..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/action.hh +++ /dev/null @@ -1,206 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_ACTION_HH -#define REACTOR_CPP_ACTION_HH - -#include "assert.hh" -#include "environment.hh" -#include "fwd.hh" -#include "logical_time.hh" -#include "reactor.hh" -#include "time_barrier.hh" -#include "value_ptr.hh" - -#include -#include -#include - -namespace reactor { - -class BaseAction : public ReactorElement { -private: - std::set triggers_{}; - std::set schedulers_{}; - const Duration min_delay_{}; - const bool logical_{true}; - bool present_{false}; - -protected: - void register_trigger(Reaction* reaction); - void register_scheduler(Reaction* reaction); - - virtual void setup() noexcept { present_ = true; } - virtual void cleanup() noexcept { present_ = false; } - - /** - * Use the given condition variable and lock to wait until the given tag it - * safe to process. The waiting is interrupted when the condition variable is - * notified (or has a spurious wakeup) and a call to the given `abort_waiting` - * function returns true. or until the condition variable is notified. - * - * Returns false if the wait was interrupted and true otherwise. True - * indicates that the tag is safe to process. - */ - virtual auto acquire_tag(const Tag& tag, std::unique_lock& lock, std::condition_variable& cv, - const std::function& abort_waiting) -> bool { - reactor_assert(!logical_); - return PhysicalTimeBarrier::acquire_tag(tag, lock, cv, abort_waiting); - } - - BaseAction(const std::string& name, Reactor* container, bool logical, Duration min_delay) - : ReactorElement(name, ReactorElement::Type::Action, container) - , min_delay_(min_delay) - , logical_(logical) {} - BaseAction(const std::string& name, Environment* environment, bool logical, Duration min_delay) - : ReactorElement(name, ReactorElement::Type::Action, environment) - , min_delay_(min_delay) - , logical_(logical) { - environment->register_input_action(this); - } - -public: - [[nodiscard]] auto inline triggers() const noexcept -> const auto& { return triggers_; } - [[nodiscard]] auto inline schedulers() const noexcept -> const auto& { return schedulers_; } - [[nodiscard]] auto inline is_logical() const noexcept -> bool { return logical_; } - [[nodiscard]] auto inline is_physical() const noexcept -> bool { return !logical_; } - [[nodiscard]] auto inline min_delay() const noexcept -> Duration { return min_delay_; } - [[nodiscard]] auto inline is_present() const noexcept -> bool { return present_; } - - friend class Reaction; - friend class Scheduler; -}; - -template class Action : public BaseAction { -private: - ImmutableValuePtr value_ptr_{nullptr}; - - std::map> events_; - std::mutex mutex_events_; - -protected: - void setup() noexcept override; - void cleanup() noexcept final; - - Action(const std::string& name, Reactor* container, bool logical, Duration min_delay) - : BaseAction(name, container, logical, min_delay) {} - Action(const std::string& name, Environment* environment, bool logical, Duration min_delay) - : BaseAction(name, environment, logical, min_delay) {} - -public: - // Normally, we should lock the mutex while moving to make this - // fully thread safe. However, we rely assembly happening before - // execution and hence can ignore the mutex. - Action(Action&& action) noexcept - : BaseAction(std::move(action)) {} - auto operator=(Action&& action) noexcept -> Action& { - BaseAction::operator=(std::move(action)); - return *this; - } - - Action(const Action& action) = delete; - auto operator=(const Action& action) -> Action& = delete; - - ~Action() override = default; - - void startup() final {} - void shutdown() final {} - - template void schedule(const ImmutableValuePtr& value_ptr, Dur delay = Dur::zero()); - auto schedule_at(const ImmutableValuePtr& value_ptr, const Tag& tag) -> bool; - - template void schedule(MutableValuePtr&& value_ptr, Dur delay = Dur::zero()) { - schedule(ImmutableValuePtr(std::forward>(value_ptr)), delay); - } - - template void schedule(const T& value, Dur delay = Dur::zero()) { - schedule(make_immutable_value(value), delay); - } - - template void schedule(T&& value, Dur delay = Dur::zero()) { - schedule(make_immutable_value(std::forward(value)), delay); - } - - // Scheduling an action with nullptr value is not permitted. - template void schedule(std::nullptr_t, Dur) = delete; - - [[nodiscard]] auto get() const noexcept -> const ImmutableValuePtr& { return value_ptr_; } -}; - -template <> class Action : public BaseAction { -protected: - Action(const std::string& name, Reactor* container, bool logical, Duration min_delay) - : BaseAction(name, container, logical, min_delay) {} - Action(const std::string& name, Environment* environment, bool logical, Duration min_delay) - : BaseAction(name, environment, logical, min_delay) {} - -public: - template void schedule(Dur delay = Dur::zero()); - auto schedule_at(const Tag& tag) -> bool; - - void startup() final {} - void shutdown() final {} -}; - -template class PhysicalAction : public Action { -public: - PhysicalAction(const std::string& name, Reactor* container) - : Action(name, container, false, Duration::zero()) { - // all physical actions act as input actions to the program as they can be - // scheduled from external threads - container->environment()->register_input_action(this); - } -}; - -template class LogicalAction : public Action { -public: - LogicalAction(const std::string& name, Reactor* container, Duration min_delay = Duration::zero()) - : Action(name, container, true, min_delay) {} -}; - -class Timer : public BaseAction { -private: - const Duration offset_{}; - const Duration period_{}; - - void cleanup() noexcept final; - -public: - Timer(const std::string& name, Reactor* container, Duration period = Duration::zero(), - Duration offset = Duration::zero()) - : BaseAction(name, container, true, Duration::zero()) - , offset_(offset) - , period_(period) {} - - void startup() final; - void shutdown() override {} - - [[nodiscard]] auto offset() const noexcept -> const Duration& { return offset_; } - - [[nodiscard]] auto period() const noexcept -> const Duration& { return period_; } -}; - -class StartupTrigger : public Timer { -public: - StartupTrigger(const std::string& name, Reactor* container) - : Timer(name, container) {} -}; - -class ShutdownTrigger : public Timer { -public: - ShutdownTrigger(const std::string& name, Reactor* container); - - void setup() noexcept final; - void shutdown() final; -}; - -} // namespace reactor - -#include "impl/action_impl.hh" - -#endif // REACTOR_CPP_ACTION_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/assert.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/assert.hh deleted file mode 100644 index 7565806d3f..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/assert.hh +++ /dev/null @@ -1,104 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_ASSERT_HH -#define REACTOR_CPP_ASSERT_HH - -#ifdef REACTOR_CPP_VALIDATE -constexpr bool runtime_validation = true; -#else -constexpr bool runtime_validation = false; -#endif - -#ifdef NDEBUG -constexpr bool runtime_assertion = false; -#else -constexpr bool runtime_assertion = true; -#endif - -#include "environment.hh" - -#include -#include -#include -#include - -#ifdef __linux__ -#include -#include -#endif - -// NOLINTNEXTLINE(cppcoreguidelines-macro-usage) -#define reactor_assert(x) assert(x) - -namespace reactor { -using EnvPhase = Environment::Phase; - -class ValidationError : public std::runtime_error { -private: - static auto build_message(std::string_view msg) noexcept -> std::string; - -public: - explicit ValidationError(const std::string_view msg) - : std::runtime_error(build_message(msg)) {} -}; - -#ifdef __linux__ -constexpr std::size_t MAX_STACK_SIZE{10}; - -inline void print_debug_backtrace() { - void* array[10]; // NOLINT - // get void*'s for all entries on the stack - int size = backtrace((void**)array, MAX_STACK_SIZE); - backtrace_symbols_fd((void**)array, size, STDERR_FILENO); -} -#endif - -constexpr inline void validate([[maybe_unused]] bool condition, [[maybe_unused]] const std::string_view message) { - if constexpr (runtime_validation) { // NOLINT - if (!condition) { -#ifdef __linux__ - print_debug_backtrace(); -#endif - throw ValidationError(message); - } - } -} - -template constexpr auto extract_value(E enum_value) -> typename std::underlying_type::type { - return static_cast::type>(enum_value); -} - -inline void assert_phase([[maybe_unused]] const ReactorElement* ptr, [[maybe_unused]] EnvPhase phase) { - if constexpr (runtime_assertion) { // NOLINT - if (ptr->environment()->phase() != phase) { - auto enum_value_to_name = [](EnvPhase phase) -> std::string { - const std::map conversation_map = { - // NOLINT - {EnvPhase::Construction, "Construction"}, {EnvPhase::Assembly, "Assembly"}, - {EnvPhase::Startup, "Startup"}, {EnvPhase::Execution, "Execution"}, - {EnvPhase::Shutdown, "Shutdown"}, {EnvPhase::Deconstruction, "Deconstruction"}}; - // in C++20 use .contains() - if (conversation_map.find(phase) != std::end(conversation_map)) { - return conversation_map.at(phase); - } - return "Unknown Phase: Value: " + std::to_string(extract_value(phase)); - }; -#ifdef __linux__ - print_debug_backtrace(); -#endif - - // C++20 std::format - throw ValidationError("Expected Phase: " + enum_value_to_name(phase) + - " Current Phase: " + enum_value_to_name(ptr->environment()->phase())); - } - } -} -} // namespace reactor - -#endif // REACTOR_CPP_ASSERT_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh deleted file mode 100644 index 5494df2584..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef REACTOR_CPP_CONFIG_HH -#define REACTOR_CPP_CONFIG_HH - -// NOLINTNEXTLINE -/* #undef REACTOR_CPP_PRINT_STATISTICS */ -// NOLINTNEXTLINE -/* #undef REACTOR_CPP_TRACE */ -// NOLINTNEXTLINE -#define REACTOR_CPP_VALIDATE -// NOLINTNEXTLINE -#define REACTOR_CPP_LOG_LEVEL 3 - -#endif diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh.in b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh.in deleted file mode 100644 index 94b9d2eafc..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/config.hh.in +++ /dev/null @@ -1,13 +0,0 @@ -#ifndef REACTOR_CPP_CONFIG_HH -#define REACTOR_CPP_CONFIG_HH - -// NOLINTNEXTLINE -#cmakedefine REACTOR_CPP_PRINT_STATISTICS -// NOLINTNEXTLINE -#cmakedefine REACTOR_CPP_TRACE -// NOLINTNEXTLINE -#cmakedefine REACTOR_CPP_VALIDATE -// NOLINTNEXTLINE -#cmakedefine REACTOR_CPP_LOG_LEVEL @REACTOR_CPP_LOG_LEVEL@ - -#endif diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection.hh deleted file mode 100644 index e9c79c87eb..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection.hh +++ /dev/null @@ -1,244 +0,0 @@ -/* - * Copyright (C) 2023 TU Dresden - * All rights reserved. - * - * Authors: - * Tassilo Tanneberger - * Christian Menard - */ - -#ifndef REACTOR_CPP_CONNECTION_HH -#define REACTOR_CPP_CONNECTION_HH - -#include "action.hh" -#include "assert.hh" -#include "environment.hh" -#include "fwd.hh" -#include "logical_time.hh" -#include "port.hh" -#include "reaction.hh" -#include "reactor.hh" -#include "time.hh" -#include "time_barrier.hh" - -namespace reactor { - -template class Connection : public Action { -private: - Port* upstream_port_{nullptr}; - std::set*> downstream_ports_{}; - -protected: - Connection(const std::string& name, Reactor* container, bool is_logical, Duration min_delay) - : Action(name, container, is_logical, min_delay) {} - Connection(const std::string& name, Environment* environment, bool is_logical, Duration min_delay) - : Action(name, environment, is_logical, min_delay) {} - - [[nodiscard]] auto downstream_ports() -> auto& { return downstream_ports_; } - [[nodiscard]] auto downstream_ports() const -> const auto& { return downstream_ports_; } - [[nodiscard]] auto upstream_port() -> auto* { return upstream_port_; } - [[nodiscard]] auto upstream_port() const -> const auto* { return upstream_port_; } - - virtual auto upstream_set_callback() noexcept -> PortCallback = 0; - -public: - virtual void bind_upstream_port(Port* port) { - reactor_assert(upstream_port_ == nullptr); - upstream_port_ = port; - port->register_set_callback(upstream_set_callback()); - } - - virtual void bind_downstream_port(Port* port) { - [[maybe_unused]] bool result = this->downstream_ports_.insert(port).second; - reactor_assert(result); - }; -}; - -template class BaseDelayedConnection : public Connection { -protected: - BaseDelayedConnection(const std::string& name, Reactor* container, bool is_logical, Duration delay) - : Connection(name, container, is_logical, delay) {} - BaseDelayedConnection(const std::string& name, Environment* environment, bool is_logical, Duration delay) - : Connection(name, environment, is_logical, delay) {} - - inline auto upstream_set_callback() noexcept -> PortCallback override { - return [this](const BasePort& port) { - // We know that port must be of type Port - auto& typed_port = reinterpret_cast&>(port); // NOLINT - if constexpr (std::is_same::value) { - this->schedule(); - } else { - this->schedule(std::move(typed_port.get())); - } - }; - } - -public: - void setup() noexcept override { - Action::setup(); - - if constexpr (std::is_same::value) { - for (auto port : this->downstream_ports()) { - port->set(); - } - } else { - for (auto port : this->downstream_ports()) { - port->set(std::move(this->get())); - } - } - } -}; - -template class DelayedConnection : public BaseDelayedConnection { -public: - DelayedConnection(const std::string& name, Reactor* container, Duration delay) - : BaseDelayedConnection(name, container, true, delay) {} -}; - -template class PhysicalConnection : public BaseDelayedConnection { -public: - PhysicalConnection(const std::string& name, Reactor* container, Duration delay) - : BaseDelayedConnection(name, container, false, delay) {} -}; - -template class EnclaveConnection : public BaseDelayedConnection { -private: - LogicalTimeBarrier logical_time_barrier_; - -protected: - log::NamedLogger log_; // NOLINT - - EnclaveConnection(const std::string& name, Environment* enclave, const Duration& delay) - : BaseDelayedConnection(name, enclave, false, delay) - , log_{this->fqn()} {} - -public: - EnclaveConnection(const std::string& name, Environment* enclave) - : BaseDelayedConnection(name, enclave, false, Duration::zero()) - , log_{this->fqn()} {} - - inline auto upstream_set_callback() noexcept -> PortCallback override { - return [this](const BasePort& port) { - // We know that port must be of type Port - auto& typed_port = reinterpret_cast&>(port); // NOLINT - const auto* scheduler = port.environment()->scheduler(); - // This callback will be called from a reaction executing in the context - // of the upstream port. Hence, we can retrieve the current tag directly - // without locking. - auto tag = Tag::from_logical_time(scheduler->logical_time()); - bool result{false}; - if constexpr (std::is_same::value) { - result = this->schedule_at(tag); - } else { - result = this->schedule_at(std::move(typed_port.get()), tag); - } - reactor_assert(result); - }; - } - - inline auto acquire_tag(const Tag& tag, std::unique_lock& lock, std::condition_variable& cv, - const std::function& abort_waiting) -> bool override { - log_.debug() << "downstream tries to acquire tag " << tag; - - if (this->upstream_port() == nullptr) { - return true; - } - - if (logical_time_barrier_.try_acquire_tag(tag)) { - return true; - } - - // Insert an empty event into the upstream event queue. This ensures that we - // will get notified and woken up as soon as the tag becomes safe to process. - // It is important to unlock the mutex here. Otherwise we could enter a deadlock as - // scheduling the upstream event also requires holding the upstream mutex. - lock.unlock(); - bool result = this->upstream_port()->environment()->scheduler()->schedule_empty_async_at(tag); - lock.lock(); - - // If inserting the empty event was not successful, then this is because the upstream - // scheduler already processes a later event. In this case, it is safe to assume that - // the tag is acquired. - if (!result) { - return true; - } - - // Wait until we receive a release_tag message from upstream - return logical_time_barrier_.acquire_tag(tag, lock, cv, abort_waiting); - } - - void bind_upstream_port(Port* port) override { - Connection::bind_upstream_port(port); - port->environment()->scheduler()->register_release_tag_callback([this](const LogicalTime& tag) { - logical_time_barrier_.release_tag(tag); - log_.debug() << "upstream released tag " << tag; - this->environment()->scheduler()->notify(); - }); - } -}; - -template class DelayedEnclaveConnection : public EnclaveConnection { -public: - DelayedEnclaveConnection(const std::string& name, Environment* enclave, Duration delay) - : EnclaveConnection(name, enclave, delay) {} - - inline auto upstream_set_callback() noexcept -> PortCallback override { - return [this](const BasePort& port) { - // We know that port must be of type Port - auto& typed_port = reinterpret_cast&>(port); // NOLINT - const auto* scheduler = port.environment()->scheduler(); - // This callback will be called from a reaction executing in the context - // of the upstream port. Hence, we can retrieve the current tag directly - // without locking. - auto tag = Tag::from_logical_time(scheduler->logical_time()).delay(this->min_delay()); - bool result{false}; - if constexpr (std::is_same::value) { - result = this->schedule_at(tag); - } else { - result = this->schedule_at(std::move(typed_port.get()), tag); - } - reactor_assert(result); - }; - } - - inline auto acquire_tag(const Tag& tag, std::unique_lock& lock, std::condition_variable& cv, - const std::function& abort_waiting) -> bool override { - // Since this is a delayed connection, we can go back in time and need to - // acquire the latest upstream tag that can create an event at the given - // tag. We also need to consider that given a delay d and a tag g=(t, n), - // for any value of n, g + d = (t, 0). Hence, we need to quire a tag with - // the highest possible microstep value. - auto upstream_tag = tag.subtract(this->min_delay()); - return EnclaveConnection::acquire_tag(upstream_tag, lock, cv, abort_waiting); - } -}; - -template class PhysicalEnclaveConnection : public EnclaveConnection { -public: - PhysicalEnclaveConnection(const std::string& name, Environment* enclave) - : EnclaveConnection(name, enclave) {} - - inline auto upstream_set_callback() noexcept -> PortCallback override { - return [this](const BasePort& port) { - // We know that port must be of type Port - auto& typed_port = reinterpret_cast&>(port); // NOLINT - if constexpr (std::is_same::value) { - this->schedule(); - } else { - this->schedule(std::move(typed_port.get())); - } - }; - } - - inline auto acquire_tag(const Tag& tag, std::unique_lock& lock, std::condition_variable& cv, - const std::function& abort_waiting) -> bool override { - this->log_.debug() << "downstream tries to acquire tag " << tag; - return PhysicalTimeBarrier::acquire_tag(tag, lock, cv, abort_waiting); - } - - void bind_upstream_port(Port* port) override { Connection::bind_upstream_port(port); } -}; - -} // namespace reactor - -#endif // REACTOR_CPP_CONNECTION_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection_endpoint.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection_endpoint.hh deleted file mode 100644 index 6e5d7bdc68..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/connection_endpoint.hh +++ /dev/null @@ -1,83 +0,0 @@ -#ifndef REACTOR_CPP_CONNECTION_ENDPOINT_HH -#define REACTOR_CPP_CONNECTION_ENDPOINT_HH - -#include -#include "port.hh" - -/* -base classes for endpoints for federated comms -*/ - -namespace reactor { - -/* -Downstream inherits from Action bec it produces event on receiver end -*/ -template -class DownstreamEndpoint : public Action { - protected: - std::set*> ports_; - - virtual void schedule_this(/*value here*/) { - if constexpr (std::is_same::value) { - this->schedule(); - } else { - //this->schedule(std::move(value here)); - } - } - - - public: - DownstreamEndpoint(const std::string& name, Reactor* container, bool is_logical, Duration min_delay) - : Action(name, container, is_logical, min_delay) {} - DownstreamEndpoint(const std::string& name, Environment* environment, bool is_logical, Duration min_delay) - : Action(name, environment, is_logical, min_delay) {} - - void add_port(Port* port) { - [[maybe_unused]] bool result = ports_.insert(port).second; - reactor_assert(result); - } - - void setup() noexcept override { - Action::setup(); - - if constexpr (std::is_same::value) { - for (auto port : this->ports_) { - port->set(); - } - } else { - for (auto port : this->ports_) { - port->set(std::move(this->get())); - } - } - - } -}; - -template -class UpstreamEndpoint { - protected: - Port* port_ = nullptr; - - virtual PortCallback set_cb() { - return [this](const BasePort& port) { - auto& typed_port = reinterpret_cast&>(port); - if constexpr (std::is_same::value) { - // send void - } else { - // send std::move(typed_port.get()); - } - }; - } - - public: - void set_port(Port* port) { - reactor_assert(port_ == nullptr); - port_ = port; - port_->register_set_callback(set_cb()); - } -}; - -// reactor ns -} -#endif \ No newline at end of file diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/environment.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/environment.hh deleted file mode 100644 index 3e59a17313..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/environment.hh +++ /dev/null @@ -1,113 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_ENVIRONMENT_HH -#define REACTOR_CPP_ENVIRONMENT_HH - -#include -#include -#include - -#include "fwd.hh" -#include "reactor-cpp/logging.hh" -#include "reactor-cpp/time.hh" -#include "reactor.hh" -#include "scheduler.hh" - -namespace reactor { - -constexpr unsigned int default_number_worker = 1; -constexpr unsigned int default_max_reaction_index = 0; -constexpr bool default_run_forever = false; -constexpr bool default_fast_fwd_execution = false; - -class Environment { -public: - enum class Phase { Construction = 0, Assembly = 1, Startup = 2, Execution = 3, Shutdown = 4, Deconstruction = 5 }; - -private: - using Dependency = std::pair; - - const std::string name_{}; - const log::NamedLogger log_; - const unsigned int num_workers_{default_number_worker}; - unsigned int max_reaction_index_{default_max_reaction_index}; - bool run_forever_{default_run_forever}; - const bool fast_fwd_execution_{default_fast_fwd_execution}; - - std::set top_level_reactors_{}; - /// Set of actions that act as an input to the reactor program in this environment - std::set input_actions_{}; - std::set reactions_{}; - std::vector dependencies_{}; - - /// The environment containing this environment. nullptr if this is the top environment - Environment* containing_environment_{nullptr}; - /// Set of all environments contained by this environment - std::set contained_environments_{}; - /// Pointer to the top level environment - Environment* top_environment_{nullptr}; - - Scheduler scheduler_; - Phase phase_{Phase::Construction}; - Tag start_tag_{}; - - const Duration timeout_{}; - - void build_dependency_graph(Reactor* reactor); - void calculate_indexes(); - - std::mutex shutdown_mutex_{}; - - auto startup(const TimePoint& start_time) -> std::thread; - -public: - explicit Environment(unsigned int num_workers, bool fast_fwd_execution = default_fast_fwd_execution, - const Duration& timeout = Duration::max()); - explicit Environment(const std::string& name, Environment* containing_environment); - - auto name() -> const std::string& { return name_; } - - void register_reactor(Reactor* reactor); - void register_input_action(BaseAction* action); - void assemble(); - auto startup() -> std::thread; - void sync_shutdown(); - void async_shutdown(); - - // Debugging methods - void export_dependency_graph(const std::string& path); - void dump_to_yaml(const std::string& path); - - static void dump_trigger_to_yaml(std::ofstream& yaml, const BaseAction& trigger); - static void dump_instance_to_yaml(std::ofstream& yaml, const Reactor& reactor); - static void dump_port_to_yaml(std::ofstream& yaml, const BasePort& port); - static void dump_reaction_to_yaml(std::ofstream& yaml, const Reaction& reaction); - - [[nodiscard]] auto top_level_reactors() const noexcept -> const auto& { return top_level_reactors_; } - [[nodiscard]] auto phase() const noexcept -> Phase { return phase_; } - [[nodiscard]] auto scheduler() const noexcept -> const Scheduler* { return &scheduler_; } - - auto scheduler() noexcept -> Scheduler* { return &scheduler_; } - - [[nodiscard]] auto logical_time() const noexcept -> const LogicalTime& { return scheduler_.logical_time(); } - [[nodiscard]] auto start_tag() const noexcept -> const Tag& { return start_tag_; } - [[nodiscard]] auto timeout() const noexcept -> const Duration& { return timeout_; } - - static auto physical_time() noexcept -> TimePoint { return get_physical_time(); } - - [[nodiscard]] auto num_workers() const noexcept -> unsigned int { return num_workers_; } - [[nodiscard]] auto fast_fwd_execution() const noexcept -> bool { return fast_fwd_execution_; } - [[nodiscard]] auto run_forever() const noexcept -> bool { return run_forever_; } - [[nodiscard]] auto max_reaction_index() const noexcept -> unsigned int { return max_reaction_index_; } - - friend Scheduler; -}; -} // namespace reactor - -#endif // REACTOR_CPP_ENVIRONMENT_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/fwd.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/fwd.hh deleted file mode 100644 index 6266b10f46..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/fwd.hh +++ /dev/null @@ -1,31 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_FWD_HH -#define REACTOR_CPP_FWD_HH - -#include - -namespace reactor { - -class BaseAction; -class BasePort; -class Environment; -class Reaction; -class Reactor; -class Scheduler; -class Tag; - -template class Action; -template class Port; - -using PortCallback = std::function; - -} // namespace reactor - -#endif // REACTOR_CPP_FWD_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/action_impl.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/action_impl.hh deleted file mode 100644 index 1e25fc7f77..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/action_impl.hh +++ /dev/null @@ -1,106 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_IMPL_ACTION_IMPL_HH -#define REACTOR_CPP_IMPL_ACTION_IMPL_HH - -#include "../assert.hh" -#include "../environment.hh" -#include -#include - -namespace reactor { - -template template void Action::schedule(const ImmutableValuePtr& value_ptr, Dur delay) { - Duration time_delay = std::chrono::duration_cast(delay); - reactor::validate(time_delay >= Duration::zero(), "Schedule cannot be called with a negative delay!"); - reactor::validate(value_ptr != nullptr, "Actions may not be scheduled with a nullptr value!"); - - auto* scheduler = environment()->scheduler(); - if (is_logical()) { - time_delay += this->min_delay(); - auto tag = Tag::from_logical_time(scheduler->logical_time()).delay(time_delay); - - scheduler->schedule_sync(this, tag); - events_[tag] = value_ptr; - } else { - std::lock_guard lock{mutex_events_}; - // We must call schedule_async while holding the mutex, because otherwise - // the scheduler could already start processing the event that we schedule - // and call setup() on this action before we insert the value in events_. - // Holding both the local mutex mutex_events_ and the scheduler mutex (in - // schedule async) should not lead to a deadlock as the scheduler will - // only hold one of the two mutexes at once. - auto tag = scheduler->schedule_async(this, time_delay); - events_[tag] = value_ptr; - } -} - -template void Action::schedule(Dur delay) { - Duration time_delay = std::chrono::duration_cast(delay); - reactor::validate(time_delay >= Duration::zero(), "Schedule cannot be called with a negative delay!"); - auto* scheduler = environment()->scheduler(); - if (is_logical()) { - time_delay += this->min_delay(); - auto tag = Tag::from_logical_time(scheduler->logical_time()).delay(time_delay); - scheduler->schedule_sync(this, tag); - } else { - scheduler->schedule_async(this, time_delay); - } -} - -template auto Action::schedule_at(const ImmutableValuePtr& value_ptr, const Tag& tag) -> bool { - reactor::validate(value_ptr != nullptr, "Actions may not be scheduled with a nullptr value!"); - - auto* scheduler = environment()->scheduler(); - if (is_logical()) { - if (tag <= scheduler->logical_time()) { - return false; - } - - scheduler->schedule_sync(this, tag); - events_[tag] = value_ptr; - } else { - std::lock_guard lock{mutex_events_}; - // We must call schedule_async while holding the mutex, because otherwise - // the scheduler could already start processing the event that we schedule - // and call setup() on this action before we insert the value in events_. - // Holding both the local mutex mutex_events_ and the scheduler mutex (in - // schedule async) should not lead to a deadlock as the scheduler will - // only hold one of the two mutexes at once. - bool result = scheduler->schedule_async_at(this, tag); - if (result) { - events_[tag] = value_ptr; - } - return result; - } - return true; -} - -template void Action::setup() noexcept { - BaseAction::setup(); - if (value_ptr_ == nullptr) { // only do this once, even if the action was triggered multiple times - // lock if this is a physical action - std::unique_lock lock = - is_logical() ? std::unique_lock() : std::unique_lock(mutex_events_); - const auto& node = events_.extract(events_.begin()); - reactor_assert(!node.empty()); - reactor_assert(node.key() == environment()->scheduler()->logical_time()); - value_ptr_ = std::move(node.mapped()); - } - reactor_assert(value_ptr_ != nullptr); -} - -template void Action::cleanup() noexcept { - BaseAction::cleanup(); - value_ptr_ = nullptr; -} - -} // namespace reactor - -#endif diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/port_impl.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/port_impl.hh deleted file mode 100644 index 8e1b7f1e69..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/impl/port_impl.hh +++ /dev/null @@ -1,51 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_IMPL_PORT_IMPL_HH -#define REACTOR_CPP_IMPL_PORT_IMPL_HH - -#include "../assert.hh" -#include "../environment.hh" -#include "reactor-cpp/port.hh" - -namespace reactor { - -template [[maybe_unused]] auto Port::typed_outward_bindings() const noexcept -> const std::set*>& { - // HACK this cast is ugly but should be safe as long as we only allow to - // bind with Port*. The alternative would be to copy the entire set and - // cast each element individually, which is also ugly... - return reinterpret_cast*>&>(outward_bindings()); // NOLINT C++20 std::bit_cast -} - -template auto Port::typed_inward_binding() const noexcept -> Port* { - // we can use a static cast here since we know that this port is always - // connected with another Port. - return static_cast*>(inward_binding()); -} - -template void Port::set(const ImmutableValuePtr& value_ptr) { - reactor::validate(!has_inward_binding(), "set() may only be called on ports that do not have an inward " - "binding!"); - reactor::validate(value_ptr != nullptr, "Ports may not be set to nullptr!"); - - auto scheduler = environment()->scheduler(); - this->value_ptr_ = std::move(value_ptr); - scheduler->set_port(this); - this->present_ = true; -} - -template auto Port::get() const noexcept -> const ImmutableValuePtr& { - if (has_inward_binding()) { - return typed_inward_binding()->get(); - } - return value_ptr_; -} - -} // namespace reactor - -#endif // REACTOR_CPP_IMPL_PORT_IMPL_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logging.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logging.hh deleted file mode 100644 index 088e46079d..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logging.hh +++ /dev/null @@ -1,109 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_LOGGING_HH -#define REACTOR_CPP_LOGGING_HH - -#include "reactor-cpp/config.hh" -#include "reactor-cpp/time.hh" -#include -#include -#include -#include -#include -#include - -namespace reactor::log { - -template class BaseLogger {}; - -template <> class BaseLogger { -private: - using Lock = std::unique_lock; - - const std::string log_prefix_{}; - inline static std::mutex mutex_{}; // NOLINT - Lock lock_{}; - -public: - explicit BaseLogger(const std::string& log_prefix) - : log_prefix_(log_prefix) - , lock_(mutex_) { - std::cerr << log_prefix; - } - BaseLogger(const BaseLogger&) = delete; - auto operator=(const BaseLogger&) -> BaseLogger& = delete; - BaseLogger(BaseLogger&&) = delete; - auto operator=(BaseLogger&&) -> BaseLogger& = delete; - - template auto operator<<(const T& msg) -> BaseLogger& { - std::cerr << msg; // NOLINT - return *this; - } - - ~BaseLogger() { std::cerr << std::endl; } -}; - -template <> class BaseLogger { -public: - explicit BaseLogger([[maybe_unused]] const std::string& /*unused*/) {} - BaseLogger(const BaseLogger&) = delete; - auto operator=(const BaseLogger&) -> BaseLogger& = delete; - BaseLogger(BaseLogger&&) = delete; - auto operator=(BaseLogger&&) -> BaseLogger& = delete; - - template auto operator<<([[maybe_unused]] const T& /*unused*/) const -> const BaseLogger& { return *this; } - - ~BaseLogger() = default; -}; - -constexpr bool debug_enabled = 4 <= REACTOR_CPP_LOG_LEVEL; -constexpr bool info_enabled = 3 <= REACTOR_CPP_LOG_LEVEL; -constexpr bool warn_enabled = 2 <= REACTOR_CPP_LOG_LEVEL; -constexpr bool error_enabled = 1 <= REACTOR_CPP_LOG_LEVEL; - -struct Debug : BaseLogger { - Debug() - : BaseLogger("[DEBUG] "){}; -}; -struct Info : BaseLogger { - Info() - : BaseLogger("[INFO] "){}; -}; -struct Warn : BaseLogger { - Warn() - : BaseLogger("[WARN] "){}; -}; -struct Error : BaseLogger { - Error() - : BaseLogger("[ERROR] "){}; -}; - -class NamedLogger { -private: - const std::string debug_prefix_{}; - const std::string info_prefix_{}; - const std::string warn_prefix_{}; - const std::string error_prefix_{}; - -public: - NamedLogger(const std::string& name) - : debug_prefix_("[DEBUG] (" + name + ") ") - , info_prefix_("[INFO] (" + name + ") ") - , warn_prefix_("[WARN] (" + name + ") ") - , error_prefix_("[ERROR] (" + name + ") ") {} - - [[nodiscard]] auto debug() const -> BaseLogger { return BaseLogger(debug_prefix_); } - [[nodiscard]] auto info() const -> BaseLogger { return BaseLogger(info_prefix_); } - [[nodiscard]] auto warn() const -> BaseLogger { return BaseLogger(warn_prefix_); } - [[nodiscard]] auto error() const -> BaseLogger { return BaseLogger(error_prefix_); } -}; - -} // namespace reactor::log - -#endif // REACTOR_CPP_LOGGING_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logical_time.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logical_time.hh deleted file mode 100644 index 49650636ea..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/logical_time.hh +++ /dev/null @@ -1,100 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_LOGICAL_TIME_HH -#define REACTOR_CPP_LOGICAL_TIME_HH - -#include "time.hh" - -namespace reactor { -using mstep_t = unsigned long; // at least 32 bit -class LogicalTime; - -class Tag { -private: - TimePoint time_point_{}; - mstep_t micro_step_{0}; - - Tag(const TimePoint& time_point, const mstep_t& micro_step) - : time_point_{time_point} - , micro_step_{micro_step} {} - -public: - Tag() = default; - Tag(Tag&&) = default; - Tag(const Tag&) = default; - auto operator=(const Tag&) -> Tag& = default; - auto operator=(Tag&&) -> Tag& = default; - ~Tag() = default; - - [[nodiscard]] auto time_point() const noexcept -> const TimePoint& { return time_point_; } - [[nodiscard]] auto micro_step() const noexcept -> const mstep_t& { return micro_step_; } - - [[nodiscard]] static auto from_physical_time(TimePoint time_point) noexcept -> Tag; - [[nodiscard]] static auto from_logical_time(const LogicalTime& logical_time) noexcept -> Tag; - - [[nodiscard]] static auto max_for_timepoint(TimePoint time_point) noexcept -> Tag; - - [[nodiscard]] auto delay(Duration offset = Duration::zero()) const noexcept -> Tag; - [[nodiscard]] auto subtract(Duration offset = Duration::zero()) const noexcept -> Tag; - [[nodiscard]] auto decrement() const noexcept -> Tag; -}; - -// define all the comparison operators -auto operator==(const Tag& lhs, const Tag& rhs) noexcept -> bool; -auto inline operator!=(const Tag& lhs, const Tag& rhs) noexcept -> bool { return !(lhs == rhs); } -auto operator<(const Tag& lhs, const Tag& rhs) noexcept -> bool; -auto inline operator>(const Tag& lhs, const Tag& rhs) noexcept -> bool { return rhs < lhs; } -auto inline operator<=(const Tag& lhs, const Tag& rhs) noexcept -> bool { return !(lhs > rhs); } -auto inline operator>=(const Tag& lhs, const Tag& rhs) noexcept -> bool { return !(lhs < rhs); } - -class LogicalTime { -private: - TimePoint time_point_{}; - mstep_t micro_step_{0}; - -public: - void advance_to(const Tag& tag); - void advance_to(const LogicalTime& time); - - [[nodiscard]] auto time_point() const noexcept -> TimePoint { return time_point_; } - [[nodiscard]] auto micro_step() const noexcept -> mstep_t { return micro_step_; } -}; - -// c++20 starship operator will save us this mess -auto operator==(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool; -auto inline operator!=(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { - return !(logical_time == tag); -} -auto operator<(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool; -auto operator>(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool; -auto inline operator<=(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { - return !(logical_time > tag); -} -auto inline operator>=(const LogicalTime& logical_time, const Tag& tag) noexcept -> bool { - return !(logical_time < tag); -} -auto inline operator==(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { return logical_time == tag; } -auto inline operator!=(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { - return !(logical_time == tag); -} -auto inline operator<(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { return logical_time > tag; } -auto inline operator>(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { return logical_time < tag; } -auto inline operator<=(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { - return !(tag > logical_time); -} -auto inline operator>=(const Tag& tag, const LogicalTime& logical_time) noexcept -> bool { - return !(tag < logical_time); -} - -auto operator<<(std::ostream& os, const Tag& tag) -> std::ostream&; -auto operator<<(std::ostream& os, const LogicalTime& tag) -> std::ostream&; - -} // namespace reactor - -#endif // REACTOR_CPP_LOGICAL_TIME_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/multiport.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/multiport.hh deleted file mode 100644 index cd8cbec815..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/multiport.hh +++ /dev/null @@ -1,119 +0,0 @@ -/* - * Copyright (C) 2022 TU Dresden - * All rights reserved. - * - * Authors: - * Tassilo Tanneberger - */ - -#ifndef REACTOR_CPP_MULTIPORT_HH -#define REACTOR_CPP_MULTIPORT_HH - -#include -#include -#include -#include -#include -#include - -#include "assert.hh" -#include "fwd.hh" - -namespace reactor { - -class BaseMultiport { // NOLINT cppcoreguidelines-special-member-functions,-warnings-as-errors -private: - std::atomic size_{0}; - std::vector present_ports_{}; - - // record that the port with the given index has been set - inline void set_present(std::size_t index); - - // reset the list of set port indexes - inline void reset() noexcept { size_.store(0, std::memory_order_relaxed); } - - [[nodiscard]] auto get_set_callback(std::size_t index) noexcept -> PortCallback; - const PortCallback clean_callback_{[this]([[maybe_unused]] const BasePort& port) { this->reset(); }}; - - [[nodiscard]] auto get_clean_callback() const noexcept -> const PortCallback& { return clean_callback_; } - -protected: - [[nodiscard]] inline auto present_ports() const -> const auto& { return present_ports_; } - [[nodiscard]] inline auto present_ports_size() const -> auto { return size_.load(); } - - inline void present_ports_reserve(size_t n) { present_ports_.reserve(n); } - - void register_port(BasePort& port, size_t idx); - -public: - BaseMultiport() = default; - ~BaseMultiport() = default; -}; - -template > -class Multiport : public BaseMultiport { // NOLINT cppcoreguidelines-special-member-functions -protected: - std::vector ports_{}; // NOLINT cppcoreguidelines-non-private-member-variables-in-classes - -public: - using value_type = typename A::value_type; - using size_type = typename A::size_type; - - using iterator = typename std::vector::iterator; - using const_iterator = typename std::vector::const_iterator; - - Multiport() noexcept = default; - ~Multiport() noexcept = default; - - auto operator==(const Multiport& other) const noexcept -> bool { - return std::equal(std::begin(ports_), std::end(ports_), std::begin(other.ports_), std::end(other.ports_)); - } - auto operator!=(const Multiport& other) const noexcept -> bool { return !(*this == other); }; - inline auto operator[](std::size_t index) noexcept -> T& { return ports_[index]; } - inline auto operator[](std::size_t index) const noexcept -> const T& { return ports_[index]; } - - inline auto begin() noexcept -> iterator { return ports_.begin(); }; - inline auto begin() const noexcept -> const_iterator { return ports_.begin(); }; - inline auto cbegin() const noexcept -> const_iterator { return ports_.cbegin(); }; - inline auto end() noexcept -> iterator { return ports_.end(); }; - inline auto end() const noexcept -> const_iterator { return ports_.end(); }; - inline auto cend() const noexcept -> const_iterator { return ports_.cend(); }; - - inline auto size() const noexcept -> size_type { return ports_.size(); }; - [[nodiscard]] inline auto empty() const noexcept -> bool { return ports_.empty(); }; - - [[nodiscard]] inline auto present_indices_unsorted() const noexcept -> std::vector { - return std::vector(std::begin(present_ports()), std::begin(present_ports()) + present_ports_size()); - } - - [[nodiscard]] inline auto present_indices_sorted() const noexcept -> std::vector { - std::vector indices(std::begin(present_ports()), std::begin(present_ports()) + present_ports_size()); - std::sort(std::begin(indices), std::end(indices)); - return indices; - } -}; - -template > class ModifableMultiport : public Multiport { // NOLINT -public: - ModifableMultiport() - : Multiport() {} // NOLINT - ~ModifableMultiport() = default; - - inline void reserve(std::size_t size) noexcept { - this->ports_.reserve(size); - this->present_ports_reserve(size); - } - - inline void push_back(const T& elem) noexcept { - this->ports_.push_back(elem); - this->register_port(this->ports_.back(), this->ports_.size() - 1); - } - - template inline void emplace_back(Args&&... args) noexcept { - this->ports_.emplace_back(args...); - this->register_port(this->ports_.back(), this->ports_.size() - 1); - } -}; -} // namespace reactor - -#endif // REACTOR_CPP_MULTIPORT_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/port.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/port.hh deleted file mode 100644 index 6e5cfbc8b9..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/port.hh +++ /dev/null @@ -1,178 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_PORT_HH -#define REACTOR_CPP_PORT_HH - -#include -#include - -#include "assert.hh" -#include "fwd.hh" -#include "multiport.hh" -#include "reactor.hh" -#include "value_ptr.hh" - -namespace reactor { - -enum class PortType { Input, Output, Delay }; - -class BasePort : public ReactorElement { -private: - BasePort* inward_binding_{nullptr}; - std::set outward_bindings_{}; - const PortType type_; - - std::set dependencies_{}; - std::set triggers_{}; - std::set anti_dependencies_{}; - - PortCallback set_callback_{nullptr}; - PortCallback clean_callback_{nullptr}; - -protected: - bool present_{false}; // NOLINT cppcoreguidelines-non-private-member-variables-in-classes - - BasePort(const std::string& name, PortType type, Reactor* container) - : ReactorElement(name, match_port_enum(type), container) - , type_(type) {} - - void base_bind_to(BasePort* port); - void register_dependency(Reaction* reaction, bool is_trigger) noexcept; - void register_antidependency(Reaction* reaction) noexcept; - virtual void cleanup() = 0; - - static auto match_port_enum(PortType type) noexcept -> ReactorElement::Type { - switch (type) { - case PortType::Output: - return ReactorElement::Type::Output; - case PortType::Input: - return ReactorElement::Type::Input; - default: - return ReactorElement::Type::Port; - }; - } - - inline void invoke_set_callback() noexcept { - if (set_callback_ != nullptr) { - set_callback_(*this); - } - } - - inline void invoke_clean_callback() noexcept { - if (clean_callback_ != nullptr) { - clean_callback_(*this); - } - } - -public: - [[nodiscard]] inline auto is_input() const noexcept -> bool { return type_ == PortType::Input; } - [[nodiscard]] inline auto is_output() const noexcept -> bool { return type_ == PortType::Output; } - [[nodiscard]] inline auto is_present() const noexcept -> bool { - if (has_inward_binding()) { - return inward_binding()->is_present(); - } - return present_; - }; - - [[nodiscard]] inline auto has_inward_binding() const noexcept -> bool { return inward_binding_ != nullptr; } - [[nodiscard]] inline auto has_outward_bindings() const noexcept -> bool { return !outward_bindings_.empty(); } - [[nodiscard]] inline auto has_dependencies() const noexcept -> bool { return !dependencies_.empty(); } - [[nodiscard]] inline auto has_anti_dependencies() const noexcept -> bool { return !anti_dependencies_.empty(); } - - [[nodiscard]] inline auto inward_binding() const noexcept -> BasePort* { return inward_binding_; } - [[nodiscard]] inline auto outward_bindings() const noexcept -> const auto& { return outward_bindings_; } - - [[nodiscard]] inline auto triggers() const noexcept -> const auto& { return triggers_; } - [[nodiscard]] inline auto dependencies() const noexcept -> const auto& { return dependencies_; } - [[nodiscard]] inline auto anti_dependencies() const noexcept -> const auto& { return anti_dependencies_; } - [[nodiscard]] inline auto port_type() const noexcept -> PortType { return type_; } - - void register_set_callback(const PortCallback& callback); - void register_clean_callback(const PortCallback& callback); - - friend class Reaction; - friend class Scheduler; -}; - -template class Port : public BasePort { -private: - ImmutableValuePtr value_ptr_{nullptr}; - - void cleanup() noexcept final { - value_ptr_ = nullptr; - present_ = false; - invoke_clean_callback(); - } - -public: - using value_type = T; - - Port(const std::string& name, PortType type, Reactor* container) - : BasePort(name, type, container) {} - - void bind_to(Port* port) { base_bind_to(port); } - [[nodiscard]] auto typed_inward_binding() const noexcept -> Port*; - [[nodiscard]] auto typed_outward_bindings() const noexcept -> const std::set*>&; - - virtual void set(const ImmutableValuePtr& value_ptr); - void set(MutableValuePtr&& value_ptr) { set(ImmutableValuePtr(std::forward>(value_ptr))); } - void set(const T& value) { set(make_immutable_value(value)); } - void set(T&& value) { set(make_immutable_value(std::forward(value))); } - // Setting a port to nullptr is not permitted. - void set(std::nullptr_t) = delete; - void startup() final {} - void shutdown() final {} - - auto get() const noexcept -> const ImmutableValuePtr&; -}; - -template <> class Port : public BasePort { -private: - void cleanup() noexcept final { - present_ = false; - invoke_clean_callback(); - } - -public: - using value_type = void; - - Port(const std::string& name, PortType type, Reactor* container) - : BasePort(name, type, container) {} - - void bind_to(Port* port) { base_bind_to(port); } - [[nodiscard]] auto typed_inward_binding() const noexcept -> Port*; - [[nodiscard]] auto typed_outward_bindings() const noexcept -> const std::set*>&; - - void set(); - - void startup() final {} - void shutdown() final {} -}; - -template class Input : public Port { // NOLINT -public: - Input(const std::string& name, Reactor* container) - : Port(name, PortType::Input, container) {} - - Input(Input&&) = default; // NOLINT(performance-noexcept-move-constructor) -}; - -template class Output : public Port { // NOLINT -public: - Output(const std::string& name, Reactor* container) - : Port(name, PortType::Output, container) {} - - Output(Output&&) = default; // NOLINT(performance-noexcept-move-constructor) -}; - -} // namespace reactor - -#include "impl/port_impl.hh" - -#endif // REACTOR_CPP_PORT_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reaction.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reaction.hh deleted file mode 100644 index acb58a3cf1..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reaction.hh +++ /dev/null @@ -1,76 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_REACTION_HH -#define REACTOR_CPP_REACTION_HH - -#include -#include - -#include "reactor.hh" - -namespace reactor { - -class Reaction : public ReactorElement { // NOLINT -private: - std::set action_triggers_; - std::set scheduable_actions_; - std::set port_trigger_; - std::set antidependencies_; - std::set dependencies_; - - const int priority_; - unsigned int index_{}; - - std::function body_{nullptr}; - - Duration deadline_{Duration::zero()}; - std::function deadline_handler_{nullptr}; - - void set_deadline_impl(Duration deadline, const std::function& handler); - -public: - Reaction(const std::string& name, int priority, Reactor* container, std::function body); - - ~Reaction() override = default; - - void declare_trigger(BaseAction* action); - void declare_trigger(BasePort* port); - void declare_schedulable_action(BaseAction* action); - void declare_antidependency(BasePort* port); - void declare_dependency(BasePort* port); - - [[nodiscard]] auto action_triggers() const noexcept -> const auto& { return action_triggers_; } - - [[nodiscard]] auto port_triggers() const noexcept -> const auto& { return port_trigger_; } - - [[maybe_unused]] [[nodiscard]] auto antidependencies() const noexcept -> const auto& { return antidependencies_; } - - [[nodiscard]] auto dependencies() const noexcept -> const auto& { return dependencies_; } - - [[maybe_unused]] [[nodiscard]] auto scheduable_actions() const noexcept -> const auto& { return scheduable_actions_; } - - [[nodiscard]] auto priority() const noexcept -> int { return priority_; } - - void startup() final {} - void shutdown() final {} - void trigger(); - void set_index(unsigned index); - - template void set_deadline(Dur deadline, const std::function& handler) { - set_deadline_impl(std::chrono::duration_cast(deadline), handler); - } - - [[nodiscard]] auto has_deadline() const noexcept -> bool { return deadline_ != Duration::zero(); } - - [[nodiscard]] auto index() const noexcept -> unsigned int { return index_; } -}; - -} // namespace reactor - -#endif // REACTOR_CPP_REACTION_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor-cpp.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor-cpp.hh deleted file mode 100644 index bb854625d7..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor-cpp.hh +++ /dev/null @@ -1,26 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_REACTOR_CPP_HH -#define REACTOR_CPP_REACTOR_CPP_HH - -// include everything that is needed to use reactor-cpp -#include "action.hh" -#include "connection.hh" -#include "connection_endpoint.hh" -#include "ros2_connection_endpoint.hh" -#include "environment.hh" -#include "logging.hh" -#include "logical_time.hh" -#include "multiport.hh" -#include "port.hh" -#include "reaction.hh" -#include "reactor.hh" -#include "time.hh" - -#endif // REACTOR_CPP_REACTOR_CPP_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor.hh deleted file mode 100644 index 255754b1b6..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/reactor.hh +++ /dev/null @@ -1,97 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_REACTOR_HH -#define REACTOR_CPP_REACTOR_HH - -#include -#include -#include - -#include "fwd.hh" -#include "logical_time.hh" - -namespace reactor { -class ReactorElement { // NOLINT -private: - const std::string name_{}; - std::string fqn_{}; - - // The reactor owning this element - Reactor* const container_{nullptr}; - Environment* environment_{}; - - auto fqn_detail(std::stringstream& string_stream) const noexcept -> std::stringstream&; - -public: - enum class Type { Action, Port, Reaction, Reactor, Input, Output }; - - ReactorElement(const std::string& name, Type type, Reactor* container); - ReactorElement(const std::string& name, Type type, Environment* environment); - virtual ~ReactorElement() = default; - - // not copyable, but movable - ReactorElement(const ReactorElement&) = delete; - ReactorElement(ReactorElement&&) = default; - - [[nodiscard]] auto container() const noexcept -> Reactor* { return container_; } - - [[nodiscard]] auto inline name() const noexcept -> const std::string& { return name_; } - [[nodiscard]] auto inline fqn() const noexcept -> const std::string& { return fqn_; } - [[nodiscard]] auto inline environment() noexcept -> Environment* { return environment_; } - [[nodiscard]] auto inline environment() const noexcept -> const Environment* { return environment_; } - - [[nodiscard]] auto inline is_top_level() const noexcept -> bool { return this->container() == nullptr; } - - virtual void startup() = 0; - virtual void shutdown() = 0; -}; - -class Reactor : public ReactorElement { // NOLINT -private: - std::set actions_{}; - std::set inputs_{}; - std::set outputs_{}; - std::set reactions_{}; - std::set reactors_{}; - - void register_action(BaseAction* action); - void register_input(BasePort* port); - void register_output(BasePort* port); - void register_reaction(Reaction* reaction); - void register_reactor(Reactor* reactor); - -public: - Reactor(const std::string& name, Reactor* container); - Reactor(const std::string& name, Environment* environment); - ~Reactor() override = default; - - [[nodiscard]] auto inline actions() const noexcept -> const auto& { return actions_; } - [[nodiscard]] auto inline inputs() const noexcept -> const auto& { return inputs_; } - [[nodiscard]] auto inline outputs() const noexcept -> const auto& { return outputs_; } - [[nodiscard]] auto inline reactions() const noexcept -> const auto& { return reactions_; } - [[nodiscard]] auto inline reactors() const noexcept -> const auto& { return reactors_; } - - void startup() final; - void shutdown() final; - - virtual void assemble() = 0; - - [[nodiscard]] static auto get_physical_time() noexcept -> TimePoint; - [[nodiscard]] auto get_logical_time() const noexcept -> TimePoint; - [[nodiscard]] auto get_microstep() const noexcept -> mstep_t; - [[nodiscard]] auto get_tag() const noexcept -> Tag; - [[nodiscard]] auto get_elapsed_logical_time() const noexcept -> Duration; - [[nodiscard]] auto get_elapsed_physical_time() const noexcept -> Duration; - - friend ReactorElement; -}; - -} // namespace reactor - -#endif // REACTOR_CPP_REACTOR_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/ros2_connection_endpoint.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/ros2_connection_endpoint.hh deleted file mode 100644 index 5f562a6331..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/ros2_connection_endpoint.hh +++ /dev/null @@ -1,79 +0,0 @@ -#ifndef REACTOR_CPP_ROS2_CONNECTION_ENDPOINT_HH -#define REACTOR_CPP_ROS2_CONNECTION_ENDPOINT_HH - -#include "connection_endpoint.hh" -#include -#include -#include - -extern rclcpp::Node* lf_node; - -namespace reactor { - -template -class ROS2PubEndpoint : public UpstreamEndpoint { - private: - std::conditional_t< std::is_same::value, - rclcpp::Publisher::SharedPtr, - std::shared_ptr> > pub_; - - protected: - PortCallback set_cb() override{ - return [this](const BasePort& port) { - auto& typed_port = reinterpret_cast&>(port); - if constexpr (std::is_same::value) { - std_msgs::msg::Empty msg; - pub_->publish(msg); - } else { - pub_->publish(*typed_port.get()); - } - }; - } - - public: - ROS2PubEndpoint(const std::string& topic_name) : UpstreamEndpoint() { - if constexpr (std::is_same::value) - pub_ = lf_node->create_publisher(topic_name, 10); - else - pub_ = lf_node->create_publisher(topic_name, 10); - } - -}; - -template -class ROS2SubEndpoint : public DownstreamEndpoint { - private: - std::conditional_t< std::is_same::value, - rclcpp::Subscription::SharedPtr, - std::shared_ptr> > sub_; - - protected: - void schedule_this(std::shared_ptr p) { - if constexpr (std::is_same::value) { - this->schedule(); - } else { - this->schedule(*p.get()); - } - } - - public: - ROS2SubEndpoint(const std::string& topic_name, const std::string& name, Environment* environment, bool is_logical, Duration min_delay) - : DownstreamEndpoint(name, environment, is_logical, min_delay){ - while (!lf_node->count_publishers(topic_name)); - if constexpr (std::is_same::value) { - std::function)> f = - std::bind(&ROS2SubEndpoint::schedule_this, this, std::placeholders::_1); - sub_ = lf_node->create_subscription(topic_name, 10, - f); - } else { - std::function)> f = - std::bind(&ROS2SubEndpoint::schedule_this, this, std::placeholders::_1); - sub_ = lf_node->create_subscription(topic_name, 10, - f); - } - } -}; - -} // reactor ns - -#endif \ No newline at end of file diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/safe_vector.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/safe_vector.hh deleted file mode 100644 index 7d933a2e98..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/safe_vector.hh +++ /dev/null @@ -1,62 +0,0 @@ -/* - * Copyright (C) 2022 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_SAFE_VECTOR_HH -#define REACTOR_CPP_SAFE_VECTOR_HH - -#include -#include -#include -#include -#include - -namespace reactor { - -template class SafeVector { -private: - static constexpr std::size_t size_increment_{100}; - std::atomic write_pos_{0}; - std::size_t vector_size_{size_increment_}; - std::vector vector_{size_increment_}; - std::shared_mutex mutex_; - -public: - void push_back(const T& value) { - auto pos = write_pos_.fetch_add(1, std::memory_order_release); - - { - std::shared_lock shared_lock(mutex_); - if (pos < vector_size_) { - vector_[pos] = value; - } else { - shared_lock.unlock(); - { - std::unique_lock unique_lock(mutex_); - while (pos >= vector_size_) { - vector_size_ += size_increment_; - vector_.resize(vector_size_); - } - vector_[pos] = value; - } - } - } - } - - // careful! Using the iterators is only safe if no more elements are added to the vector! - auto begin() -> auto { return vector_.begin(); } - auto end() -> auto { return vector_.begin() + write_pos_.load(std::memory_order_acquire); } - - auto size() -> std::size_t { return write_pos_.load(std::memory_order_acquire); } - auto empty() -> bool { return size() == 0; } - - void clear() { write_pos_.store(0, std::memory_order_release); } -}; - -} // namespace reactor - -#endif // REACTOR_CPP_SAFE_VECTOR_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/scheduler.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/scheduler.hh deleted file mode 100644 index c2f7a318da..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/scheduler.hh +++ /dev/null @@ -1,197 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_SCHEDULER_HH -#define REACTOR_CPP_SCHEDULER_HH - -#include -#include -#include -#include -#include -#include -#include -#include -#include - -#include "fwd.hh" -#include "logical_time.hh" -#include "reactor-cpp/logging.hh" -#include "reactor-cpp/time.hh" -#include "safe_vector.hh" -#include "semaphore.hh" - -namespace reactor { - -using ReleaseTagCallback = std::function; - -// forward declarations -class Scheduler; -class Worker; - -class Worker { // NOLINT -private: - Scheduler& scheduler_; - const unsigned int identity_{0}; - std::thread thread_{}; - log::NamedLogger log_; - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - static thread_local const Worker* current_worker; - - void work() const; - void execute_reaction(Reaction* reaction) const; - -public: - Worker(Scheduler& scheduler, unsigned int identity, const std::string& name) - : scheduler_{scheduler} - , identity_{identity} - , log_(name) {} - Worker(Worker&& worker); // NOLINT(performance-noexcept-move-constructor) - Worker(const Worker& worker) = delete; - - void start_thread() { thread_ = std::thread(&Worker::work, this); } - void join_thread() { thread_.join(); } - - static auto current_worker_id() -> unsigned { return current_worker->identity_; } -}; - -class ReadyQueue { -private: - std::vector queue_{}; - std::atomic size_{0}; - Semaphore sem_{0}; - std::ptrdiff_t waiting_workers_{0}; - const std::ptrdiff_t num_workers_; - log::NamedLogger& log_; - -public: - explicit ReadyQueue(log::NamedLogger& log, std::ptrdiff_t num_workers) - : num_workers_(num_workers) - , log_(log) {} - - /** - * Retrieve a ready reaction from the queue. - * - * This method may be called concurrently. In case the queue is empty, the - * method blocks and waits until a ready reaction becomes available. - */ - auto pop() -> Reaction*; - - /** - * Fill the queue up with ready reactions. - * - * This method assumes that the internal queue is empty. It moves all - * reactions from the provided `ready_reactions` vector to the internal - * queue, leaving `ready_reactions` empty. - * - * Note that this method is not thread-safe. The caller needs to ensure that - * no other thread will try to read from the queue during this operation. - */ - void fill_up(std::vector& ready_reactions); -}; - -using ActionList = SafeVector; -using ActionListPtr = std::unique_ptr; - -class EventQueue { -private: - std::shared_mutex mutex_; - std::map event_queue_; - /// stores the actions triggered at the current tag - ActionListPtr triggered_actions_{nullptr}; - - std::vector action_list_pool_; - static constexpr std::size_t action_list_pool_increment_{10}; - - void fill_action_list_pool(); - -public: - EventQueue() { fill_action_list_pool(); } - - [[nodiscard]] auto empty() const -> bool { return event_queue_.empty(); } - [[nodiscard]] auto next_tag() const -> Tag; - - auto insert_event_at(const Tag& tag) -> const ActionListPtr&; - - // should only be called while holding the scheduler mutex - auto extract_next_event() -> ActionListPtr; - - // should only be called while holding the scheduler mutex - void return_action_list(ActionListPtr&& action_list); - - // should only be called while holding the scheduler mutex - void discard_events_until_tag(const Tag& tag); -}; - -class Scheduler { // NOLINT -private: - const bool using_workers_; - LogicalTime logical_time_{}; - - Environment* environment_; - std::vector workers_{}; - log::NamedLogger log_; - - std::mutex scheduling_mutex_; - std::condition_variable cv_schedule_; - - EventQueue event_queue_; - /// stores the actions triggered at the current tag - ActionListPtr triggered_actions_{nullptr}; - - std::vector> set_ports_; - std::vector> triggered_reactions_; - std::vector> reaction_queue_; - unsigned int reaction_queue_pos_{std::numeric_limits::max()}; - - ReadyQueue ready_queue_; - std::atomic reactions_to_process_{0}; - - std::atomic stop_{false}; - bool continue_execution_{true}; - - std::vector release_tag_callbacks_{}; - void release_current_tag(); - - void schedule() noexcept; - auto schedule_ready_reactions() -> bool; - void next(); - void terminate_all_workers(); - void set_port_helper(BasePort* port); - - void advance_logical_time_to(const Tag& tag); - -public: - explicit Scheduler(Environment* env); - ~Scheduler(); - - void schedule_sync(BaseAction* action, const Tag& tag); - auto schedule_async(BaseAction* action, const Duration& delay) -> Tag; - auto schedule_async_at(BaseAction* action, const Tag& tag) -> bool; - auto schedule_empty_async_at(const Tag& tag) -> bool; - - void inline notify() noexcept { cv_schedule_.notify_one(); } - - auto inline lock() noexcept -> auto { return std::unique_lock(scheduling_mutex_); } - - void set_port(BasePort* port); - - [[nodiscard]] inline auto logical_time() const noexcept -> const auto& { return logical_time_; } - - void start(); - void stop(); - - void register_release_tag_callback(const ReleaseTagCallback& callback); - - friend Worker; -}; - -} // namespace reactor - -#endif // REACTOR_CPP_SCHEDULER_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/semaphore.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/semaphore.hh deleted file mode 100644 index bc19854727..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/semaphore.hh +++ /dev/null @@ -1,45 +0,0 @@ -/* - * Copyright (C) 2021 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_SEMAPHORE_HH -#define REACTOR_CPP_SEMAPHORE_HH - -#include -#include -#include - -namespace reactor { - -class Semaphore { -private: - int count_; - std::mutex mutex_{}; - std::condition_variable cv_{}; - -public: - explicit Semaphore(int count) - : count_(count) {} - - void release(int increment) { - { - std::lock_guard lock_guard(mutex_); - count_ += increment; - } - cv_.notify_all(); - } - - void acquire() { - std::unique_lock lock_guard(mutex_); - cv_.wait(lock_guard, [&]() { return count_ != 0; }); - count_--; - } -}; - -} // namespace reactor - -#endif // REACTOR_CPP_SEMAPHORE_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/statistics.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/statistics.hh deleted file mode 100644 index 4f51374440..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/statistics.hh +++ /dev/null @@ -1,99 +0,0 @@ -/* - * Copyright (C) 2023 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_STATISTICS_HH -#define REACTOR_CPP_STATISTICS_HH - -#include - -#include "reactor-cpp/config.hh" -#include "reactor-cpp/logging.hh" - -namespace reactor { - -class Statistics { -private: -#ifdef REACTOR_CPP_PRINT_STATISTICS - constexpr static bool enabled_{true}; -#else - constexpr static bool enabled_{false}; -#endif - - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t reactor_instances_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t connections_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t reactions_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t actions_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t ports_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t processed_events_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t processed_reactions_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t triggered_actions_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t set_ports_{0}; - // NOLINTNEXTLINE(cppcoreguidelines-avoid-non-const-global-variables) - inline static std::atomic_size_t scheduled_actions_{0}; - - inline static void increment(std::atomic_size_t& counter) { - if constexpr (enabled_) { - counter.fetch_add(1, std::memory_order_release); - } - } - -public: - inline static void increment_reactor_instances() { increment(reactor_instances_); } - inline static void increment_connections() { increment(connections_); } - inline static void increment_reactions() { increment(reactions_); } - inline static void increment_actions() { increment(actions_); } - inline static void increment_ports() { increment(ports_); } - inline static void increment_processed_events() { increment(processed_events_); } - inline static void increment_processed_reactions() { increment(processed_reactions_); } - inline static void increment_triggered_actions() { increment(triggered_actions_); } - inline static void increment_set_ports() { increment(set_ports_); } - inline static void increment_scheduled_actions() { increment(scheduled_actions_); } - - inline static auto reactor_instances() { return reactor_instances_.load(std::memory_order_acquire); } - inline static auto connections() { return connections_.load(std::memory_order_acquire); } - inline static auto reactions() { return reactions_.load(std::memory_order_acquire); } - inline static auto actions() { return actions_.load(std::memory_order_acquire); } - inline static auto ports() { return ports_.load(std::memory_order_acquire); } - inline static auto processed_events() { return processed_events_.load(std::memory_order_acquire); } - inline static auto processed_reactions() { return processed_reactions_.load(std::memory_order_acquire); } - inline static auto triggered_actions() { return triggered_actions_.load(std::memory_order_acquire); } - inline static auto set_ports() { return set_ports_.load(std::memory_order_acquire); } - inline static auto scheduled_actions() { return scheduled_actions_.load(std::memory_order_acquire); } - - inline static void print() { - if constexpr (enabled_) { - reactor::log::Info() << "-----------------------------------------------------------"; - reactor::log::Info() << "Program statistics:"; - reactor::log::Info() << " - number of reactors: " << reactor_instances(); - reactor::log::Info() << " - number of connections: " << connections(); - reactor::log::Info() << " - number of reactions " << reactions(); - reactor::log::Info() << " - number of actions: " << actions(); - reactor::log::Info() << " - number of ports: " << ports(); - reactor::log::Info() << "Execution statistics:"; - reactor::log::Info() << " - processed events: " << processed_events(); - reactor::log::Info() << " - triggered actions: " << triggered_actions(); - reactor::log::Info() << " - processed reactions: " << processed_reactions(); - reactor::log::Info() << " - set ports: " << set_ports(); - reactor::log::Info() << " - scheduled actions: " << scheduled_actions(); - reactor::log::Info() << "-----------------------------------------------------------"; - } - } -}; - -} // namespace reactor - -#endif // REACTOR_CPP_STATISTICS_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time.hh deleted file mode 100644 index da2adde14f..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time.hh +++ /dev/null @@ -1,35 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_TIME_HH -#define REACTOR_CPP_TIME_HH - -#include -#include - -namespace reactor { - -using TimePoint = std::chrono::time_point; -using Duration = std::chrono::nanoseconds; - -auto inline get_physical_time() -> TimePoint { return std::chrono::system_clock::now(); } - -inline namespace operators { - -auto operator<<(std::ostream& os, TimePoint tp) -> std::ostream&; - -auto operator<<(std::ostream& os, std::chrono::seconds dur) -> std::ostream&; -auto operator<<(std::ostream& os, std::chrono::milliseconds dur) -> std::ostream&; -auto operator<<(std::ostream& os, std::chrono::microseconds dur) -> std::ostream&; -auto operator<<(std::ostream& os, std::chrono::nanoseconds dur) -> std::ostream&; - -} // namespace operators - -} // namespace reactor - -#endif diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time_barrier.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time_barrier.hh deleted file mode 100644 index a09501db88..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/time_barrier.hh +++ /dev/null @@ -1,77 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -#ifndef REACTOR_CPP_TIME_BARRIER_HH -#define REACTOR_CPP_TIME_BARRIER_HH - -#include "fwd.hh" -#include "logical_time.hh" -#include "time.hh" -#include -#include -#include -#include - -namespace reactor { - -class PhysicalTimeBarrier { - inline static std::atomic last_observed_physical_time_{Duration::zero()}; // NOLINT - -public: - static inline auto try_acquire_tag(const Tag& tag) -> bool { - // First, we compare against the last observed physical time. This variable - // serves as a cache for reading the physical clock. Reading from the physical - // clock can be slow and, thus, this is an optimization that ensures that we - // only read the clock when it is needed. - if (tag.time_point().time_since_epoch() < last_observed_physical_time_.load(std::memory_order_acquire)) { - return true; - } - - auto physical_time = get_physical_time(); - last_observed_physical_time_.store(physical_time.time_since_epoch(), std::memory_order_release); - - return tag.time_point() < physical_time; - } - - static inline auto acquire_tag(const Tag& tag, std::unique_lock& lock, std::condition_variable& cv, - const std::function& abort_waiting) { - if (try_acquire_tag(tag)) { - return true; - } - return !cv.wait_until(lock, tag.time_point(), abort_waiting); - } -}; - -class LogicalTimeBarrier { - std::mutex mutex_; - LogicalTime released_time_; - -public: - inline void release_tag(const LogicalTime& tag) { - std::lock_guard lock(mutex_); - released_time_.advance_to(tag); - } - - inline auto try_acquire_tag(const Tag& tag) { - std::lock_guard lock(mutex_); - return tag <= released_time_; - } - - inline auto acquire_tag(const Tag& tag, std::unique_lock& lock, std::condition_variable& cv, - const std::function& abort_waiting) -> bool { - if (try_acquire_tag(tag)) { - return true; - } - cv.wait(lock, [this, &tag, &abort_waiting]() { return try_acquire_tag(tag) || abort_waiting(); }); - return !abort_waiting(); - } -}; - -} // namespace reactor - -#endif // REACTOR_CPP_TIME_BARRIER_HH diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/trace.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/trace.hh deleted file mode 100644 index 5031b7b4c5..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/trace.hh +++ /dev/null @@ -1,92 +0,0 @@ -/* - * Copyright (C) 2020 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -// NOTE: This file is named trace.hpp (with the hpp file extension) on purpose. -// This is to exclude the file from all clang-tidy checks. This is more a hacky -// workaround than an actual solution, but apparently clang-tidy does not allow -// something nicer at the moment. - -#include - -#include "reactor-cpp/config.hh" -#include "reactor-cpp/logical_time.hh" - -// Only enable tracing if REACTOR_CPP_TRACE is set. -// Also, disable tracing if clang analytics are run as it produces many errors. -#if defined(REACTOR_CPP_TRACE) && !defined(__clang_analyzer__) - -#undef LTTNG_UST_COMPAT_API_VERSION -#define LTTNG_UST_COMPAT_API_VERSION 0 - -#undef TRACEPOINT_PROVIDER -#define TRACEPOINT_PROVIDER reactor_cpp - -#undef TRACEPOINT_INCLUDE -#define TRACEPOINT_INCLUDE "reactor-cpp/trace.hh" - -// LTTng requires this header to be included multiple times. -#ifndef REACTOR_CPP_TRACE_HH -namespace reactor { -constexpr bool tracing_enabled = true; -} -#endif - -#if !defined(REACTOR_CPP_TRACE_HH) || defined(TRACEPOINT_HEADER_MULTI_READ) -#define REACTOR_CPP_TRACE_HH - -#include - -TRACEPOINT_EVENT( - reactor_cpp, reaction_execution_starts, - TP_ARGS(int, worker_id_arg, const std::string&, reaction_name_arg, const reactor::LogicalTime&, tag_arg), - TP_FIELDS(ctf_string(reaction_name, reaction_name_arg.c_str()) ctf_integer(int, worker_id, worker_id_arg) - ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) - ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) - -TRACEPOINT_EVENT( - reactor_cpp, reaction_execution_finishes, - TP_ARGS(int, worker_id_arg, const std::string&, reaction_name_arg, const reactor::LogicalTime&, tag_arg), - TP_FIELDS(ctf_string(reaction_name, reaction_name_arg.c_str()) ctf_integer(int, worker_id, worker_id_arg) - ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) - ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) - -TRACEPOINT_EVENT( - reactor_cpp, schedule_action, - TP_ARGS(const std::string&, reactor_name_arg, const std::string&, action_name_arg, const reactor::Tag&, tag_arg), - TP_FIELDS(ctf_string(reactor_name, reactor_name_arg.c_str()) ctf_string(action_name, action_name_arg.c_str()) - ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) - ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) - -TRACEPOINT_EVENT( - reactor_cpp, trigger_reaction, - TP_ARGS(const std::string&, reactor_name_arg, const std::string&, reaction_name_arg, const reactor::LogicalTime&, - tag_arg), - TP_FIELDS(ctf_string(reactor_name, reactor_name_arg.c_str()) ctf_string(reaction_name, reaction_name_arg.c_str()) - ctf_integer(unsigned long long, timestamp_ns, tag_arg.time_point().time_since_epoch().count()) - ctf_integer(unsigned long, timestamp_microstep, tag_arg.micro_step()))) - -#endif /* REACTOR_CPP_TRACE_HH */ - -#include - -#else - -#ifndef REACTOR_CPP_TRACE_HH -// NOLINTNEXTLINE -#define REACTOR_CPP_TRACE_HH - -namespace reactor { -constexpr bool tracing_enabled = false; -} // namespace reactor - -// empty definition in case we compile without tracing -#define tracepoint(...) - -#endif // REACTOR_CPP_TRACE_HH - -#endif // REACTOR_CPP_TRACE diff --git a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/value_ptr.hh b/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/value_ptr.hh deleted file mode 100644 index 7ee9ec323a..0000000000 --- a/test/Cpp/install/reactor-cpp-default/include/reactor-cpp-default/reactor-cpp/value_ptr.hh +++ /dev/null @@ -1,599 +0,0 @@ -/* - * Copyright (C) 2019 TU Dresden - * All rights reserved. - * - * Authors: - * Christian Menard - */ - -/** - * @file Defines classes for smart value pointers as well as comparison - * operators and factory functions. - */ - -#ifndef REACTOR_CPP_VALUE_PTR_HH -#define REACTOR_CPP_VALUE_PTR_HH - -#include -#include -#include - -#include "reactor-cpp/logging.hh" - -namespace reactor { - -namespace detail { - -template class ImmutableValuePtr {}; -template class MutableValuePtr {}; - -constexpr std::size_t SIZE_THRESHOLD = 64; - -template constexpr auto is_trivial() -> bool { - return std::is_default_constructible::value && std::is_trivially_copyable::value && sizeof(T) <= SIZE_THRESHOLD; -} - -} // namespace detail - -template using MutableValuePtr = detail::MutableValuePtr()>; -template using ImmutableValuePtr = detail::ImmutableValuePtr()>; - -/** - * @rst - * Create an instance of :class:`ImmutableValuePtr`. - * - * Creates and initializes a new instance of ``T`` and returns a new - * :class:`ImmutableValuePtr` owning this value. This is analogues to - * :std-memory:`make_shared` - * @endrst - * @tparam T type of the value to be created - * @tparam Args types of ``T``'s constructor arguments. Usually, this does not - * need to be given explicitly and will be inferred automatically from the - * given ``args``. - * @param args Arguments to be forwarded to ``T``'s constructor - * @return a new immutable value pointer - */ -template auto make_immutable_value(Args&&... args) -> ImmutableValuePtr { - if constexpr (detail::is_trivial()) { - return ImmutableValuePtr(T(std::forward(args)...)); - } else { - return ImmutableValuePtr(std::make_shared(std::forward(args)...)); - } -} - -/** - * @rst - * Create an instance of :class:`MutableValuePtr`. - - * Creates and initializes a new instance of ``T`` and returns a new - * :class:`MutableValuePtr` owning this value. This is analogues to - * :std-memory:`make_unique`. - * @endrst - * @tparam T type of the value to be created - * @tparam Args types of ``T``'s constructor arguments. Usually, this does not - * need to be given explicitly and will be inferred automatically from the - * given ``args``. - * @param args Arguments to be forwarded to ``T``'s constructor - * @return a new mutable value pointer - */ -template auto make_mutable_value(Args&&... args) -> MutableValuePtr { - if constexpr (detail::is_trivial()) { - return MutableValuePtr(T(std::forward(args)...)); - } else { - return MutableValuePtr(std::make_unique(std::forward(args)...)); - } -} - -namespace detail { - -/** - * @brief Smart pointer to a mutable value. - * - * @rst - * Manages the lifetime of a value in conjunction with - * :class:`ImmutableValuePtr`. Implements ownership semantics and enforces - * unique ownership of a mutable value. :class:`MutableValuePtr` internally - * wraps around :std-memory:`unique_ptr`. The unique ownership ensures that no - * other reactor can reference the value while it is allowed to change. In - * order to share the associated value, an instance of :class:`MutableValuePtr` - * needs to be converted to an :class:`ImmutableValuePtr` making the associated - * value immutable. - * @endrst - * @tparam T type of the value managed by this class - * @author Christian Menard - */ -template class MutableValuePtr { -private: - /// The internal unique smart pointer that this class builds upon. - std::unique_ptr internal_ptr; - - /** - * Constructor from an existing raw pointer. - * - * @rst - * Constructs a :class:`MutableValuePtr` such that is obtains ownership of - * ``value``. This is intended only for usage by the - * :func:`make_mutable_value()` factory function and the - * :func:`make_immutable_copy()` method of :class:`ImmutableValuePtr`. - * @endrst - */ - explicit MutableValuePtr(std::unique_ptr&& value) - : internal_ptr(std::move(value)) {} - -public: - /** - * Default constructor. - * @rst - * Constructs a :class:`MutableValuePtr` that owns nothing. - * @endrst - */ - constexpr MutableValuePtr() = default; - ~MutableValuePtr() = default; - - /** - * Copy constructor (Deleted). - * @rst - * Since :class:`MutableValuePtr` enforces unique ownership, there cannot - * be two instances pointing to the same object and therefore copying cannot - * be allowed, - * @endrst - */ - MutableValuePtr(const MutableValuePtr&) = delete; - - /** - * Move constructor. - * @rst - * Constructs a :class:`MutableValuePtr` by transferring ownership from - * another :class:`MutableValuePtr` instance ``ptr``. ``ptr`` looses - * ownership and will own nothing. - * @endrst - */ - MutableValuePtr(MutableValuePtr&& ptr) noexcept = default; - - /** - * Constructor from ``nullptr``. - * @rst - * Constructs a :class:`MutableValuePtr` that owns nothing. - * @endrst - */ - explicit constexpr MutableValuePtr(std::nullptr_t) - : internal_ptr(nullptr) {} - - /** - * Move assignment operator. - * @rst - * Transfers ownership from ``ptr`` to this :class:`MutableValuePtr` - * instance. If this instance previously owned a value, the value is deleted. - * @endrst - */ - auto operator=(MutableValuePtr&& ptr) noexcept -> MutableValuePtr& { - this->internal_ptr = std::move(ptr.internal_ptr); - return *this; - } - - /** - * Copy assignment operator (Deleted). - * @rst - * Since :class:`MutableValuePtr` enforces unique ownership, there cannot - * be two instances pointing to the same object and therefore copying cannot - * be allowed, - * @endrst - */ - auto operator=(const MutableValuePtr& ptr) -> MutableValuePtr& = delete; - - /** - * Assignment operator from ``nullptr``. - * - * Releases ownership. If this instance previously owned a value, the - * value is deleted. - */ - auto operator=(std::nullptr_t) noexcept -> MutableValuePtr& { - this->internal_ptr = nullptr; - return *this; - } - - /** - * Retrieve a raw pointer to the managed value. - */ - [[nodiscard]] auto get() const noexcept -> T* { return internal_ptr.get(); } - - /** - * Cast to ``bool``. Checks if there is an associated value. - * - * @return ``false`` if there is no associated value (``internal_ptr == - * nullptr``), ``true`` otherwise - */ - explicit operator bool() const { return get() == nullptr; } - - /** - * Dereference the pointer to the managed value. - * - * The behavior is undefined if ``get() == nullptr``. - */ - auto operator*() const -> T& { return *get(); } - /** - * Dereference the pointer to the managed value. - * - * Provides access to members of the associated value via ``->``. The - * behavior is undefined if ``get() == nullptr``. - */ - auto operator->() const -> T* { return get(); } // NOLINT - - // Give ImmutableValuePtr access to the private constructor. This is required - // for creating a MutableValuePtr from an ImmutableValuePtr in - // get_mutable_copy() - friend class ImmutableValuePtr; - - // Give the factory function make_mutable_value() access to the private - // constructor - template - // NOLINTNEXTLINE(readability-redundant-declaration) - friend auto reactor::make_mutable_value(Args&&... args) -> reactor::MutableValuePtr; -}; - -/** - * @brief Smart pointer to an immutable value. - * - * @rst - * Manages the lifetime of a value in conjunction with - * :class:`MutableValuePtr`. Implements ownership semantics and allows shared - * ownership of an immutable value. :class:`ImmutableValuePtr` internally - * wraps around :std-memory:`shared_ptr`. The shared ownership semantics - * enables multiple reactors to share a value which is only safe if the value - * is immutable. In order to modify the associated value, an instance of - * :class:`ImmutableValuePtr` needs to be converted to an - * :class:`MutableValuePtr` making the associated value mutable. This can be - * achieved by calling :func:`get_mutable_copy()`. - * @endrst - * @tparam T type of the value managed by this class - * @author Christian Menard - */ -template class ImmutableValuePtr { -public: - /// A type alias that adds ``const`` to ``T`` - using const_T = typename std::add_const::type; - -private: - /// The internal shared smart pointer that this class builds upon. - std::shared_ptr internal_ptr; - - /** - * Constructor from an existing raw pointer. - * - * @rst - * Constructs an :class:`ImutableValuePtr` such that is obtains ownership - * of ``value``. This is intended only for usage by the - * :func:`make_immutable_value()` factory function. - * @endrst - */ - explicit ImmutableValuePtr(std::shared_ptr&& value) - : internal_ptr(std::move(value)) {} - -public: - /** - * Default constructor. - * @rst - * Constructs an :class:`ImmutableValuePtr` that owns nothing. - * @endrst - */ - constexpr ImmutableValuePtr() - : internal_ptr(nullptr) {} - - ~ImmutableValuePtr() = default; - - /** - * Copy constructor. - * @rst - * Constructs an :class:`ImmutableValuePtr` by copying another - * :class:`ImmutableValuePtr` instance ``ptr``. Both pointers have the same - * associated value and, therefore, share ownership. - * @endrst - */ - ImmutableValuePtr(const ImmutableValuePtr& ptr) = default; - /** - * Move constructor. - * @rst - * Constructs an :class:`ImmutableValuePtr` by transferring ownership from - * another :class:`ImmutableValuePtr` instance ``ptr``. ``ptr`` looses - * ownership and will own nothing. - * @endrst - */ - ImmutableValuePtr(ImmutableValuePtr&& ptr) noexcept = default; - /** - * Constructor from ``nullptr``. - * @rst - * Constructs an :class:`ImmutableValuePtr` that owns nothing. - * @endrst - */ - explicit constexpr ImmutableValuePtr(std::nullptr_t) - : internal_ptr(nullptr) {} - /** - * @rst - * Move constructor from :class:`MutableValuePtr`. - * - * Constructs an :class:`ImmutableValuePtr` by transferring ownership from a - * :class:`MutableValuePtr` instance ``ptr``. ``ptr`` looses ownership and - * will own nothing. This effectively converts the mutable value initially - * associated with ``ptr`` to an immutable value. - * @endrst - */ - explicit ImmutableValuePtr(MutableValuePtr&& ptr) - : internal_ptr(std::move(ptr.internal_ptr)) {} - - /** - * Assignment operator from ``nullptr``. - * - * @rst - * Releases ownership. If this instance previously owned a value that is not - * owned by any other instance of class:`ImmutableValuePtr`, the value is - * deleted. - * @endrst - */ - auto operator=(std::nullptr_t) -> ImmutableValuePtr& { - this->internal_ptr = nullptr; - return *this; - } - /** - * @rst - * Assignment operator from another :class:`ImmutableValuePtr`. - * - * Replaces the managed value of this instance by the one managed by - * ``ptr``. Both instances share the ownership. If this instance previously - * owned a value that is not owned by any other instance of - * class:`ImmutableValuePtr`, the value is deleted. - * @endrst - */ - auto operator=(const ImmutableValuePtr& ptr) -> ImmutableValuePtr& { // NOLINT(cert-oop54-cpp) - this->internal_ptr = ptr.internal_ptr; - return *this; - } - /** - * @rst - * Move assignment operator from another :class:`ImmutableValuePtr`. - * - * Replaces the managed value of this instance by the one managed by ``ptr``. - * This moves the ownership from ``ptr`` to this instance. If this instance - * previously owned a value that is not owned by any other instance of - * class:`ImmutableValuePtr`, the value is deleted. - * @endrst - */ - auto operator=(ImmutableValuePtr&& ptr) noexcept -> ImmutableValuePtr& { - this->internal_ptr = std::move(ptr.internal_ptr); - return *this; - } - - /** - * Retrieve a raw pointer to the managed value. - * - * Since the associated value is immutable, this only provides const access - * to the value. - */ - [[nodiscard]] auto get() const -> const_T* { return internal_ptr.get(); } - - /** - * Cast to ``bool``. Checks if there is an associated value. - * - * @return ``false`` if there is no associated value (``internal_ptr == - * nullptr``), ``true`` otherwise - */ - explicit operator bool() const { return get() == nullptr; } - - /** - * Dereference the pointer to the managed value. - * - * Since the associated value is immutable, this only provides const access - * to the value. - * - * The behavior is undefined if ``get() == nullptr``. - */ - auto operator*() const -> const_T& { return *get(); } - /** - * Dereference the pointer to the managed value. - * - * Since the associated value is immutable, this only provides const access - * to the value. - * - * Provides access to members of the associated value via ``->``. The - * behavior is undefined if ``get() == nullptr``. - */ - auto operator->() const -> const_T* { return get(); } - - /** - * Create a mutable copy of the value associated with this instance. - * - * @rst - * This is the only allowed mechanism to convert a :class:`ImmutableValuePtr` - * to a :class:`MutableValuePtr`. In fact, it does not perform a conversion - * but copies the associated value of this instance and gives ownership of - * the copy to a newly created :class:`MutableValuePtr`. - * @endrst - * - * Requires that ``T`` is copy constructable. The behavior is undefined if - * ``get() == nullptr``. - * @return a mutable value pointer - */ - [[nodiscard]] auto get_mutable_copy() const -> MutableValuePtr { - return MutableValuePtr(std::make_unique(*internal_ptr)); - } - - // Give the factory function make_mutable_value() access to the private - // constructor - template - // NOLINTNEXTLINE(readability-redundant-declaration) - friend auto reactor::make_immutable_value(Args&&... args) -> reactor::ImmutableValuePtr; -}; - -template class MutableValuePtr { -private: - T value_{}; - bool valid_{false}; - - explicit MutableValuePtr(const T& value) - : value_{value} - , valid_{true} {} - -public: - constexpr MutableValuePtr() = default; - ~MutableValuePtr() = default; - MutableValuePtr(const MutableValuePtr&) = delete; - MutableValuePtr(MutableValuePtr&& ptr) noexcept = default; - - explicit constexpr MutableValuePtr(std::nullptr_t) {} - - auto operator=(MutableValuePtr&& ptr) noexcept -> MutableValuePtr& = default; - auto operator=(const MutableValuePtr& ptr) -> MutableValuePtr& = delete; - - auto operator=(std::nullptr_t) noexcept -> MutableValuePtr& { - valid_ = false; - return *this; - } - - [[nodiscard]] auto get() noexcept -> T* { return valid_ ? &value_ : nullptr; } - [[nodiscard]] auto get() const noexcept -> const T* { return valid_ ? &value_ : nullptr; } - - explicit operator bool() const { return valid_; } - - auto operator*() -> T& { return value_; } - auto operator*() const -> const T& { return value_; } - - auto operator->() -> T* { return get(); } - auto operator->() const -> const T* { return get(); } - - // Give ImmutableValuePtr access to the private constructor. This is required - // for creating a MutableValuePtr from an ImmutableValuePtr in - // get_mutable_copy() - friend class ImmutableValuePtr; - - // Give the factory function make_mutable_value() access to the private - // constructor - template - // NOLINTNEXTLINE(readability-redundant-declaration) - friend auto reactor::make_mutable_value(Args&&... args) -> reactor::MutableValuePtr; -}; - -template class ImmutableValuePtr { -public: - /// A type alias that adds ``const`` to ``T`` - using const_T = typename std::add_const::type; - -private: - T value_{}; - bool valid_{false}; - - explicit ImmutableValuePtr(T value) - : value_{value} - , valid_{true} {} - -public: - constexpr ImmutableValuePtr() = default; - ~ImmutableValuePtr() = default; - ImmutableValuePtr(const ImmutableValuePtr& ptr) = default; - ImmutableValuePtr(ImmutableValuePtr&& ptr) noexcept = default; - - explicit constexpr ImmutableValuePtr(std::nullptr_t) {} - explicit ImmutableValuePtr(MutableValuePtr&& ptr) - : value_(ptr.value_) - , valid_(ptr.valid_) {} - - auto operator=(std::nullptr_t) -> ImmutableValuePtr& { - this->valid_ = false; - return *this; - } - auto operator=(const ImmutableValuePtr& ptr) -> ImmutableValuePtr& = default; - auto operator=(ImmutableValuePtr&& ptr) noexcept -> ImmutableValuePtr& = default; - - [[nodiscard]] auto get() const -> const_T* { return valid_ ? &value_ : nullptr; } - - explicit operator bool() const { return valid_; } - - auto operator*() const -> const_T& { return value_; } - auto operator->() const -> const_T* { return get(); } - - [[nodiscard]] auto get_mutable_copy() const -> MutableValuePtr { return MutableValuePtr(*get()); } - - // Give the factory function make_mutable_value() access to the private - // constructor - template - // NOLINTNEXTLINE(readability-redundant-declaration) - friend auto reactor::make_immutable_value(Args&&... args) -> reactor::ImmutableValuePtr; -}; - -// Comparison operators - -template -auto operator==(const MutableValuePtr& ptr1, const MutableValuePtr& ptr2) noexcept - -> bool { - return ptr1.get() == ptr2.get(); -} -template -auto operator==(const ImmutableValuePtr& ptr1, const ImmutableValuePtr& ptr2) noexcept - -> bool { - return ptr1.get() == ptr2.get(); -} -template -auto operator==(const ImmutableValuePtr& ptr1, const MutableValuePtr& ptr2) noexcept - -> bool { - return ptr1.get() == ptr2.get(); -} -template -auto operator==(const MutableValuePtr& ptr1, const ImmutableValuePtr& ptr2) noexcept - -> bool { - return ptr1.get() == ptr2.get(); -} -template -auto operator==(const MutableValuePtr& ptr1, std::nullptr_t) noexcept -> bool { - return ptr1.get() == nullptr; -} -template -auto operator==(std::nullptr_t, const MutableValuePtr& ptr2) noexcept -> bool { - return ptr2.get() == nullptr; -} -template -auto operator==(const ImmutableValuePtr& ptr1, std::nullptr_t) noexcept -> bool { - return ptr1.get() == nullptr; -} -template -auto operator==(std::nullptr_t, const ImmutableValuePtr& ptr1) noexcept -> bool { - return ptr1.get() == nullptr; -} - -template -auto operator!=(const MutableValuePtr& ptr1, const MutableValuePtr& ptr2) noexcept - -> bool { - return ptr1.get() != ptr2.get(); -} - -template -auto operator!=(const ImmutableValuePtr& ptr1, const ImmutableValuePtr& ptr2) -> bool { - return ptr1.get() != ptr2.get(); -} -template -auto operator!=(const ImmutableValuePtr& ptr1, const MutableValuePtr& ptr2) -> bool { - return ptr1.get() != ptr2.get(); -} -template -auto operator!=(const MutableValuePtr& ptr1, const ImmutableValuePtr& ptr2) -> bool { - return ptr1.get() != ptr2.get(); -} -template -auto operator!=(const MutableValuePtr& ptr1, std::nullptr_t) -> bool { - return ptr1.get() != nullptr; -} -template -auto operator!=(std::nullptr_t, const MutableValuePtr& ptr1) -> bool { - return ptr1.get() != nullptr; -} -template -auto operator!=(const ImmutableValuePtr& ptr1, std::nullptr_t) -> bool { - return ptr1.get() != nullptr; -} -template -auto operator!=(std::nullptr_t, const ImmutableValuePtr& ptr1) -> bool { - return ptr1.get() != nullptr; -} - -} // namespace detail - -} // namespace reactor - -#endif diff --git a/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so b/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so deleted file mode 120000 index 1bf6a46f51..0000000000 --- a/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so +++ /dev/null @@ -1 +0,0 @@ -libreactor-cpp-default.so.1 \ No newline at end of file diff --git a/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so.0.0.1 b/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so.0.0.1 deleted file mode 100644 index a22e3bc2199b6d579760d600233dc823205a96eb..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 175976 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 EFNQgt-v9sr diff --git a/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so.1 b/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so.1 deleted file mode 120000 index c862a24458..0000000000 --- a/test/Cpp/install/reactor-cpp-default/lib/libreactor-cpp-default.so.1 +++ /dev/null @@ -1 +0,0 @@ -libreactor-cpp-default.so.0.0.1 \ No newline at end of file diff --git a/test/Cpp/install/reactor-cpp-default/share/colcon-core/packages/reactor-cpp-default b/test/Cpp/install/reactor-cpp-default/share/colcon-core/packages/reactor-cpp-default deleted file mode 100644 index 869a8e1781..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/colcon-core/packages/reactor-cpp-default +++ /dev/null @@ -1 +0,0 @@ -LTTngUST:Threads \ No newline at end of file diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig-release.cmake b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig-release.cmake deleted file mode 100644 index 7701157e38..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig-release.cmake +++ /dev/null @@ -1,19 +0,0 @@ -#---------------------------------------------------------------- -# Generated CMake target import file for configuration "Release". -#---------------------------------------------------------------- - -# Commands may need to know the format version. -set(CMAKE_IMPORT_FILE_VERSION 1) - -# Import target "reactor-cpp-default" for configuration "Release" -set_property(TARGET reactor-cpp-default APPEND PROPERTY IMPORTED_CONFIGURATIONS RELEASE) -set_target_properties(reactor-cpp-default PROPERTIES - IMPORTED_LOCATION_RELEASE "${_IMPORT_PREFIX}/lib/libreactor-cpp-default.so.0.0.1" - IMPORTED_SONAME_RELEASE "libreactor-cpp-default.so.1" - ) - -list(APPEND _cmake_import_check_targets reactor-cpp-default ) -list(APPEND _cmake_import_check_files_for_reactor-cpp-default "${_IMPORT_PREFIX}/lib/libreactor-cpp-default.so.0.0.1" ) - -# Commands beyond this point should not need to know the version. -set(CMAKE_IMPORT_FILE_VERSION) diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig.cmake b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig.cmake deleted file mode 100644 index 790ea34dc0..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/cmake/reactor-cpp-defaultConfig.cmake +++ /dev/null @@ -1,107 +0,0 @@ -# Generated by CMake - -if("${CMAKE_MAJOR_VERSION}.${CMAKE_MINOR_VERSION}" LESS 2.8) - message(FATAL_ERROR "CMake >= 2.8.0 required") -endif() -if(CMAKE_VERSION VERSION_LESS "2.8.3") - message(FATAL_ERROR "CMake >= 2.8.3 required") -endif() -cmake_policy(PUSH) -cmake_policy(VERSION 2.8.3...3.23) -#---------------------------------------------------------------- -# Generated CMake target import file. -#---------------------------------------------------------------- - -# Commands may need to know the format version. -set(CMAKE_IMPORT_FILE_VERSION 1) - -# Protect against multiple inclusion, which would fail when already imported targets are added once more. -set(_cmake_targets_defined "") -set(_cmake_targets_not_defined "") -set(_cmake_expected_targets "") -foreach(_cmake_expected_target IN ITEMS reactor-cpp-default) - list(APPEND _cmake_expected_targets "${_cmake_expected_target}") - if(TARGET "${_cmake_expected_target}") - list(APPEND _cmake_targets_defined "${_cmake_expected_target}") - else() - list(APPEND _cmake_targets_not_defined "${_cmake_expected_target}") - endif() -endforeach() -unset(_cmake_expected_target) -if(_cmake_targets_defined STREQUAL _cmake_expected_targets) - unset(_cmake_targets_defined) - unset(_cmake_targets_not_defined) - unset(_cmake_expected_targets) - unset(CMAKE_IMPORT_FILE_VERSION) - cmake_policy(POP) - return() -endif() -if(NOT _cmake_targets_defined STREQUAL "") - string(REPLACE ";" ", " _cmake_targets_defined_text "${_cmake_targets_defined}") - string(REPLACE ";" ", " _cmake_targets_not_defined_text "${_cmake_targets_not_defined}") - message(FATAL_ERROR "Some (but not all) targets in this export set were already defined.\nTargets Defined: ${_cmake_targets_defined_text}\nTargets not yet defined: ${_cmake_targets_not_defined_text}\n") -endif() -unset(_cmake_targets_defined) -unset(_cmake_targets_not_defined) -unset(_cmake_expected_targets) - - -# Compute the installation prefix relative to this file. -get_filename_component(_IMPORT_PREFIX "${CMAKE_CURRENT_LIST_FILE}" PATH) -get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) -get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) -get_filename_component(_IMPORT_PREFIX "${_IMPORT_PREFIX}" PATH) -if(_IMPORT_PREFIX STREQUAL "/") - set(_IMPORT_PREFIX "") -endif() - -# Create imported target reactor-cpp-default -add_library(reactor-cpp-default SHARED IMPORTED) - -set_target_properties(reactor-cpp-default PROPERTIES - INTERFACE_INCLUDE_DIRECTORIES "${_IMPORT_PREFIX}/include/reactor-cpp-default" - INTERFACE_LINK_LIBRARIES "-lpthread" -) - -if(CMAKE_VERSION VERSION_LESS 2.8.12) - message(FATAL_ERROR "This file relies on consumers using CMake 2.8.12 or greater.") -endif() - -# Load information for each installed configuration. -file(GLOB _cmake_config_files "${CMAKE_CURRENT_LIST_DIR}/reactor-cpp-defaultConfig-*.cmake") -foreach(_cmake_config_file IN LISTS _cmake_config_files) - include("${_cmake_config_file}") -endforeach() -unset(_cmake_config_file) -unset(_cmake_config_files) - -# Cleanup temporary variables. -set(_IMPORT_PREFIX) - -# Loop over all imported files and verify that they actually exist -foreach(_cmake_target IN LISTS _cmake_import_check_targets) - foreach(_cmake_file IN LISTS "_cmake_import_check_files_for_${_cmake_target}") - if(NOT EXISTS "${_cmake_file}") - message(FATAL_ERROR "The imported target \"${_cmake_target}\" references the file - \"${_cmake_file}\" -but this file does not exist. Possible reasons include: -* The file was deleted, renamed, or moved to another location. -* An install or uninstall procedure did not complete successfully. -* The installation package was faulty and contained - \"${CMAKE_CURRENT_LIST_FILE}\" -but not all the files it references. -") - endif() - endforeach() - unset(_cmake_file) - unset("_cmake_import_check_files_for_${_cmake_target}") -endforeach() -unset(_cmake_target) -unset(_cmake_import_check_targets) - -# This file does not depend on other imported targets which have -# been exported from the same project but in a separate export set. - -# Commands beyond this point should not need to know the version. -set(CMAKE_IMPORT_FILE_VERSION) -cmake_policy(POP) diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.dsv b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.dsv deleted file mode 100644 index e119f32cba..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.dsv +++ /dev/null @@ -1 +0,0 @@ -prepend-non-duplicate;CMAKE_PREFIX_PATH; diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.ps1 b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.ps1 deleted file mode 100644 index d03facc1a4..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em - -colcon_prepend_unique_value CMAKE_PREFIX_PATH "$env:COLCON_CURRENT_PREFIX" diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.sh b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.sh deleted file mode 100644 index a948e685ba..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/cmake_prefix_path.sh +++ /dev/null @@ -1,3 +0,0 @@ -# generated from colcon_core/shell/template/hook_prepend_value.sh.em - -_colcon_prepend_unique_value CMAKE_PREFIX_PATH "$COLCON_CURRENT_PREFIX" diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.dsv b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.dsv deleted file mode 100644 index 89bec935bf..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.dsv +++ /dev/null @@ -1 +0,0 @@ -prepend-non-duplicate;LD_LIBRARY_PATH;lib diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.ps1 b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.ps1 deleted file mode 100644 index f6df601d0c..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.ps1 +++ /dev/null @@ -1,3 +0,0 @@ -# generated from colcon_powershell/shell/template/hook_prepend_value.ps1.em - -colcon_prepend_unique_value LD_LIBRARY_PATH "$env:COLCON_CURRENT_PREFIX\lib" diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.sh b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.sh deleted file mode 100644 index ca3c1020bb..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/hook/ld_library_path_lib.sh +++ /dev/null @@ -1,3 +0,0 @@ -# generated from colcon_core/shell/template/hook_prepend_value.sh.em - -_colcon_prepend_unique_value LD_LIBRARY_PATH "$COLCON_CURRENT_PREFIX/lib" diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.bash b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.bash deleted file mode 100644 index 67d91811a9..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.bash +++ /dev/null @@ -1,31 +0,0 @@ -# generated from colcon_bash/shell/template/package.bash.em - -# This script extends the environment for this package. - -# a bash script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - # the prefix is two levels up from the package specific share directory - _colcon_package_bash_COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`/../.." > /dev/null && pwd)" -else - _colcon_package_bash_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_bash_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source sh script of this package -_colcon_package_bash_source_script "$_colcon_package_bash_COLCON_CURRENT_PREFIX/share/reactor-cpp-default/package.sh" - -unset _colcon_package_bash_source_script -unset _colcon_package_bash_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.dsv b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.dsv deleted file mode 100644 index ce3aa140d1..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.dsv +++ /dev/null @@ -1,6 +0,0 @@ -source;share/reactor-cpp-default/hook/cmake_prefix_path.ps1 -source;share/reactor-cpp-default/hook/cmake_prefix_path.dsv -source;share/reactor-cpp-default/hook/cmake_prefix_path.sh -source;share/reactor-cpp-default/hook/ld_library_path_lib.ps1 -source;share/reactor-cpp-default/hook/ld_library_path_lib.dsv -source;share/reactor-cpp-default/hook/ld_library_path_lib.sh diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.ps1 b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.ps1 deleted file mode 100644 index 8ed00be797..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.ps1 +++ /dev/null @@ -1,116 +0,0 @@ -# generated from colcon_powershell/shell/template/package.ps1.em - -# function to append a value to a variable -# which uses colons as separators -# duplicates as well as leading separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -function colcon_append_unique_value { - param ( - $_listname, - $_value - ) - - # get values from variable - if (Test-Path Env:$_listname) { - $_values=(Get-Item env:$_listname).Value - } else { - $_values="" - } - $_duplicate="" - # start with no values - $_all_values="" - # iterate over existing values in the variable - if ($_values) { - $_values.Split(";") | ForEach { - # not an empty string - if ($_) { - # not a duplicate of _value - if ($_ -eq $_value) { - $_duplicate="1" - } - if ($_all_values) { - $_all_values="${_all_values};$_" - } else { - $_all_values="$_" - } - } - } - } - # append only non-duplicates - if (!$_duplicate) { - # avoid leading separator - if ($_all_values) { - $_all_values="${_all_values};${_value}" - } else { - $_all_values="${_value}" - } - } - - # export the updated variable - Set-Item env:\$_listname -Value "$_all_values" -} - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -function colcon_prepend_unique_value { - param ( - $_listname, - $_value - ) - - # get values from variable - if (Test-Path Env:$_listname) { - $_values=(Get-Item env:$_listname).Value - } else { - $_values="" - } - # start with the new value - $_all_values="$_value" - # iterate over existing values in the variable - if ($_values) { - $_values.Split(";") | ForEach { - # not an empty string - if ($_) { - # not a duplicate of _value - if ($_ -ne $_value) { - # keep non-duplicate values - $_all_values="${_all_values};$_" - } - } - } - } - # export the updated variable - Set-Item env:\$_listname -Value "$_all_values" -} - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -function colcon_package_source_powershell_script { - param ( - $_colcon_package_source_powershell_script - ) - # source script with conditional trace output - if (Test-Path $_colcon_package_source_powershell_script) { - if ($env:COLCON_TRACE) { - echo ". '$_colcon_package_source_powershell_script'" - } - . "$_colcon_package_source_powershell_script" - } else { - Write-Error "not found: '$_colcon_package_source_powershell_script'" - } -} - - -# a powershell script is able to determine its own path -# the prefix is two levels up from the package specific share directory -$env:COLCON_CURRENT_PREFIX=(Get-Item $PSCommandPath).Directory.Parent.Parent.FullName - -colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/reactor-cpp-default/hook/cmake_prefix_path.ps1" -colcon_package_source_powershell_script "$env:COLCON_CURRENT_PREFIX\share/reactor-cpp-default/hook/ld_library_path_lib.ps1" - -Remove-Item Env:\COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.sh b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.sh deleted file mode 100644 index 9e7f3f0966..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.sh +++ /dev/null @@ -1,87 +0,0 @@ -# generated from colcon_core/shell/template/package.sh.em - -# This script extends the environment for this package. - -# function to prepend a value to a variable -# which uses colons as separators -# duplicates as well as trailing separators are avoided -# first argument: the name of the result variable -# second argument: the value to be prepended -_colcon_prepend_unique_value() { - # arguments - _listname="$1" - _value="$2" - - # get values from variable - eval _values=\"\$$_listname\" - # backup the field separator - _colcon_prepend_unique_value_IFS=$IFS - IFS=":" - # start with the new value - _all_values="$_value" - # workaround SH_WORD_SPLIT not being set in zsh - if [ "$(command -v colcon_zsh_convert_to_array)" ]; then - colcon_zsh_convert_to_array _values - fi - # iterate over existing values in the variable - for _item in $_values; do - # ignore empty strings - if [ -z "$_item" ]; then - continue - fi - # ignore duplicates of _value - if [ "$_item" = "$_value" ]; then - continue - fi - # keep non-duplicate values - _all_values="$_all_values:$_item" - done - unset _item - # restore the field separator - IFS=$_colcon_prepend_unique_value_IFS - unset _colcon_prepend_unique_value_IFS - # export the updated variable - eval export $_listname=\"$_all_values\" - unset _all_values - unset _values - - unset _value - unset _listname -} - -# since a plain shell script can't determine its own path when being sourced -# either use the provided COLCON_CURRENT_PREFIX -# or fall back to the build time prefix (if it exists) -_colcon_package_sh_COLCON_CURRENT_PREFIX="/home/cs/Desktop/lingua-franca/test/Cpp/install/reactor-cpp-default" -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - if [ ! -d "$_colcon_package_sh_COLCON_CURRENT_PREFIX" ]; then - echo "The build time path \"$_colcon_package_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 - unset _colcon_package_sh_COLCON_CURRENT_PREFIX - return 1 - fi - COLCON_CURRENT_PREFIX="$_colcon_package_sh_COLCON_CURRENT_PREFIX" -fi -unset _colcon_package_sh_COLCON_CURRENT_PREFIX - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_sh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo "# . \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source sh hooks -_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/reactor-cpp-default/hook/cmake_prefix_path.sh" -_colcon_package_sh_source_script "$COLCON_CURRENT_PREFIX/share/reactor-cpp-default/hook/ld_library_path_lib.sh" - -unset _colcon_package_sh_source_script -unset COLCON_CURRENT_PREFIX - -# do not unset _colcon_prepend_unique_value since it might be used by non-primary shell hooks diff --git a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.zsh b/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.zsh deleted file mode 100644 index f3bdab2ba8..0000000000 --- a/test/Cpp/install/reactor-cpp-default/share/reactor-cpp-default/package.zsh +++ /dev/null @@ -1,42 +0,0 @@ -# generated from colcon_zsh/shell/template/package.zsh.em - -# This script extends the environment for this package. - -# a zsh script is able to determine its own path if necessary -if [ -z "$COLCON_CURRENT_PREFIX" ]; then - # the prefix is two levels up from the package specific share directory - _colcon_package_zsh_COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`/../.." > /dev/null && pwd)" -else - _colcon_package_zsh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -# additional arguments: arguments to the script -_colcon_package_zsh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$@" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# function to convert array-like strings into arrays -# to workaround SH_WORD_SPLIT not being set -colcon_zsh_convert_to_array() { - local _listname=$1 - local _dollar="$" - local _split="{=" - local _to_array="(\"$_dollar$_split$_listname}\")" - eval $_listname=$_to_array -} - -# source sh script of this package -_colcon_package_zsh_source_script "$_colcon_package_zsh_COLCON_CURRENT_PREFIX/share/reactor-cpp-default/package.sh" -unset convert_zsh_to_array - -unset _colcon_package_zsh_source_script -unset _colcon_package_zsh_COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/setup.bash b/test/Cpp/install/setup.bash deleted file mode 100644 index b19cab0c50..0000000000 --- a/test/Cpp/install/setup.bash +++ /dev/null @@ -1,31 +0,0 @@ -# generated from colcon_bash/shell/template/prefix_chain.bash.em - -# This script extends the environment with the environment of other prefix -# paths which were sourced when this file was generated as well as all packages -# contained in this prefix path. - -# function to source another script with conditional trace output -# first argument: the path of the script -_colcon_prefix_chain_bash_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$1" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source chained prefixes -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script -COLCON_CURRENT_PREFIX="/opt/ros/foxy" -_colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" - -# source this prefix -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script -COLCON_CURRENT_PREFIX="$(builtin cd "`dirname "${BASH_SOURCE[0]}"`" > /dev/null && pwd)" -_colcon_prefix_chain_bash_source_script "$COLCON_CURRENT_PREFIX/local_setup.bash" - -unset COLCON_CURRENT_PREFIX -unset _colcon_prefix_chain_bash_source_script diff --git a/test/Cpp/install/setup.ps1 b/test/Cpp/install/setup.ps1 deleted file mode 100644 index 412726f37d..0000000000 --- a/test/Cpp/install/setup.ps1 +++ /dev/null @@ -1,29 +0,0 @@ -# generated from colcon_powershell/shell/template/prefix_chain.ps1.em - -# This script extends the environment with the environment of other prefix -# paths which were sourced when this file was generated as well as all packages -# contained in this prefix path. - -# function to source another script with conditional trace output -# first argument: the path of the script -function _colcon_prefix_chain_powershell_source_script { - param ( - $_colcon_prefix_chain_powershell_source_script_param - ) - # source script with conditional trace output - if (Test-Path $_colcon_prefix_chain_powershell_source_script_param) { - if ($env:COLCON_TRACE) { - echo ". '$_colcon_prefix_chain_powershell_source_script_param'" - } - . "$_colcon_prefix_chain_powershell_source_script_param" - } else { - Write-Error "not found: '$_colcon_prefix_chain_powershell_source_script_param'" - } -} - -# source chained prefixes -_colcon_prefix_chain_powershell_source_script "/opt/ros/foxy\local_setup.ps1" - -# source this prefix -$env:COLCON_CURRENT_PREFIX=(Split-Path $PSCommandPath -Parent) -_colcon_prefix_chain_powershell_source_script "$env:COLCON_CURRENT_PREFIX\local_setup.ps1" diff --git a/test/Cpp/install/setup.sh b/test/Cpp/install/setup.sh deleted file mode 100644 index 3c6a57875b..0000000000 --- a/test/Cpp/install/setup.sh +++ /dev/null @@ -1,45 +0,0 @@ -# generated from colcon_core/shell/template/prefix_chain.sh.em - -# This script extends the environment with the environment of other prefix -# paths which were sourced when this file was generated as well as all packages -# contained in this prefix path. - -# since a plain shell script can't determine its own path when being sourced -# either use the provided COLCON_CURRENT_PREFIX -# or fall back to the build time prefix (if it exists) -_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX=/home/cs/Desktop/lingua-franca/test/Cpp/install -if [ ! -z "$COLCON_CURRENT_PREFIX" ]; then - _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX="$COLCON_CURRENT_PREFIX" -elif [ ! -d "$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX" ]; then - echo "The build time path \"$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX\" doesn't exist. Either source a script for a different shell or set the environment variable \"COLCON_CURRENT_PREFIX\" explicitly." 1>&2 - unset _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX - return 1 -fi - -# function to source another script with conditional trace output -# first argument: the path of the script -_colcon_prefix_chain_sh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo "# . \"$1\"" - fi - . "$1" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source chained prefixes -# setting COLCON_CURRENT_PREFIX avoids relying on the build time prefix of the sourced script -COLCON_CURRENT_PREFIX="/opt/ros/foxy" -_colcon_prefix_chain_sh_source_script "$COLCON_CURRENT_PREFIX/local_setup.sh" - - -# source this prefix -# setting COLCON_CURRENT_PREFIX avoids relying on the build time prefix of the sourced script -COLCON_CURRENT_PREFIX="$_colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX" -_colcon_prefix_chain_sh_source_script "$COLCON_CURRENT_PREFIX/local_setup.sh" - -unset _colcon_prefix_chain_sh_COLCON_CURRENT_PREFIX -unset _colcon_prefix_chain_sh_source_script -unset COLCON_CURRENT_PREFIX diff --git a/test/Cpp/install/setup.zsh b/test/Cpp/install/setup.zsh deleted file mode 100644 index a98672a90b..0000000000 --- a/test/Cpp/install/setup.zsh +++ /dev/null @@ -1,31 +0,0 @@ -# generated from colcon_zsh/shell/template/prefix_chain.zsh.em - -# This script extends the environment with the environment of other prefix -# paths which were sourced when this file was generated as well as all packages -# contained in this prefix path. - -# function to source another script with conditional trace output -# first argument: the path of the script -_colcon_prefix_chain_zsh_source_script() { - if [ -f "$1" ]; then - if [ -n "$COLCON_TRACE" ]; then - echo ". \"$1\"" - fi - . "$1" - else - echo "not found: \"$1\"" 1>&2 - fi -} - -# source chained prefixes -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script -COLCON_CURRENT_PREFIX="/opt/ros/foxy" -_colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" - -# source this prefix -# setting COLCON_CURRENT_PREFIX avoids determining the prefix in the sourced script -COLCON_CURRENT_PREFIX="$(builtin cd -q "`dirname "${(%):-%N}"`" > /dev/null && pwd)" -_colcon_prefix_chain_zsh_source_script "$COLCON_CURRENT_PREFIX/local_setup.zsh" - -unset COLCON_CURRENT_PREFIX -unset _colcon_prefix_chain_zsh_source_script diff --git a/test/Cpp/src/ros-federated/FederateCycle.lf b/test/Cpp/src/ros-federated/FederateCycle.lf new file mode 100644 index 0000000000..ad9b4c3874 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateCycle.lf @@ -0,0 +1,76 @@ +target Cpp { + ros2: true, + timeout: 5s, + ros2-dependencies: ["std_msgs"], +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/empty.hpp" +=} + +reactor Ping { + timer t(0, 100 ms) + input in: std_msgs::msg::Empty + output out: std_msgs::msg::Empty + state counter: int = 0 + state received: bool = false + + reaction(t) -> out {= + std_msgs::msg::Empty msg; + out.set(msg); + =} + + reaction(in) {= + received = true; + reactor::log::Info() << "Ping Received at " << get_elapsed_logical_time(); + auto expected = 50ms + 100ms * counter++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "Nothing received."; + exit(1); + } + =} +} + +reactor Pong { + input in: std_msgs::msg::Empty + output out: std_msgs::msg::Empty + state received: bool = false + state counter: int = 0 + + reaction(in) -> out {= + received = true; + reactor::log::Info() << "Pong Received at " << get_elapsed_logical_time(); + auto expected = 100ms * counter++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + std_msgs::msg::Empty msg; + out.set(msg); + =} + + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "Nothing received."; + exit(1); + } + =} +} + +main reactor { + @federate + ping = new Ping() + @federate + pong = new Pong() + + ping.out -> pong.in + pong.out -> ping.in after 50 ms +} diff --git a/test/Cpp/src/federated/EmptyConnection.lf b/test/Cpp/src/ros-federated/FederateEmptyConnection.lf similarity index 51% rename from test/Cpp/src/federated/EmptyConnection.lf rename to test/Cpp/src/ros-federated/FederateEmptyConnection.lf index 3513eb33e6..6eec4592f1 100644 --- a/test/Cpp/src/federated/EmptyConnection.lf +++ b/test/Cpp/src/ros-federated/FederateEmptyConnection.lf @@ -1,6 +1,6 @@ target Cpp { ros2: true, - timeout: 5 s, + timeout: 5s, ros2-dependencies: ["std_msgs"], } @@ -15,18 +15,16 @@ reactor Pub { extern rclcpp::Node* lf_node; =} - - timer t(0, 500 ms) - + timer t(0, 100 ms) output out : std_msgs::msg::Empty reaction(startup) {= - RCLCPP_INFO(lf_node->get_logger(), "Hi"); + RCLCPP_INFO(lf_node->get_logger(), "Pub here"); =} reaction(t) -> out {= - std_msgs::msg::Empty m; - out.set(m); + std_msgs::msg::Empty msg; + out.set(msg); =} } @@ -36,27 +34,39 @@ reactor Sub { extern rclcpp::Node* lf_node; =} state count: unsigned(0) + state received: bool = false input in : std_msgs::msg::Empty reaction(startup) {= - RCLCPP_INFO(lf_node->get_logger(), "Hi"); + RCLCPP_INFO(lf_node->get_logger(), "Sub here"); =} reaction(in) {= - reactor::log::Info() << "I heard"; + received = true; + reactor::log::Info() << "Received empty message at " << get_elapsed_logical_time(); + auto expected = 100ms * count; + count++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected empty message at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "Nothing received."; + exit(1); + } =} } + main reactor { @federate pub = new Pub() @federate sub = new Sub() - reaction(startup) {= - RCLCPP_INFO(lf_node->get_logger(), "Hi"); - =} - pub.out -> sub.in } \ No newline at end of file diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections.lf new file mode 100644 index 0000000000..668a5f4ea5 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections.lf @@ -0,0 +1,69 @@ +// Test data transport across hierarchy. +target Cpp { + ros2: true, + ros2-dependencies: ["std_msgs"], + timeout: 3 sec +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/int64.hpp" + + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; +=} + +reactor Source { + output out: std_msgs::msg::Int64 + timer t + + reaction(t) -> out {= + std_msgs::msg::Int64 i; + i.data = 1; + out.set(i); + =} +} + +reactor Gain { + input in: std_msgs::msg::Int64 + output out: std_msgs::msg::Int64 + + reaction(in) -> out {= + std_msgs::msg::Int64 i; + i.data = in.get()->data*2; + out.set(i); + =} +} + +reactor Print { + input in: std_msgs::msg::Int64 + + reaction(in) {= + std_msgs::msg::Int64 i = *in.get(); + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data); + if (i.data != 2) { + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 2"); + exit(1); + } + =} +} + +reactor GainContainer { + input in: std_msgs::msg::Int64 + output out: std_msgs::msg::Int64 + output out2: std_msgs::msg::Int64 + gain = new Gain() + in -> gain.in + gain.out -> out + gain.out -> out2 +} + +main reactor { + source = new Source() + container = new GainContainer() + print = new Print() + print2 = new Print() + source.out -> container.in + container.out -> print.in + container.out -> print2.in +} diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf new file mode 100644 index 0000000000..eafac73be1 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf @@ -0,0 +1,97 @@ +// Test data transport across hierarchy. +target Cpp { + ros2: true, + ros2-dependencies: ["std_msgs"], + timeout: 300 sec +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/int64.hpp" + + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; +=} + +reactor Source { + output out: std_msgs::msg::Int64 + timer t + + reaction(t) -> out {= + RCLCPP_INFO_STREAM(lf_node->get_logger(), "source sending"); + std_msgs::msg::Int64 i; + i.data = 1; + out.set(i); + =} +} + +reactor Gain { + input in: std_msgs::msg::Int64 + output out: std_msgs::msg::Int64 + + reaction(in) -> out {= + std_msgs::msg::Int64 i; + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain gaining"); + i.data = in.get()->data*2; + out.set(i); + =} +} + +reactor Print { + input in: std_msgs::msg::Int64 + + reaction(in) {= + std_msgs::msg::Int64 i = *in.get(); + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data); + if (i.data != 2) { + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 2"); + exit(1); + } + =} +} + + + +reactor GainContainer { + input in_cont: std_msgs::msg::Int64 + output out_cont: std_msgs::msg::Int64 + output out2: std_msgs::msg::Int64 + output out3: std_msgs::msg::Int64 + + // own port to federate and other way around not working yet + @federate + gain = new Gain() + in_cont -> gain.in + + reaction(in_cont) {= + //std_msgs::msg::Int64 i; + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container gaining"); + //i.data = in.get()->data*2; + //gain.in.set(i); + =} + + //reaction(gain.out) {= + // RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container reacting"); + //=} + gain.out -> out_cont + //gain.out -> out2 +} + +main reactor { + source = new Source() + + // lf says no when referencing anything except ports from instances + //reaction(source.t){= + // RCLCPP_INFO_STREAM(lf_node->get_logger(), "main reacting to source.t"); + //=} + + @federate + container = new GainContainer() + + print = new Print() + + print2 = new Print() + source.out ~> container.in_cont + container.out_cont -> print.in + container.out_cont -> print2.in after 2s +} diff --git a/test/Cpp/src/ros-federated/FederateHierarchy.lf b/test/Cpp/src/ros-federated/FederateHierarchy.lf new file mode 100644 index 0000000000..cfd2295d1b --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateHierarchy.lf @@ -0,0 +1,82 @@ +// This is a smoke test for nested federates +target Cpp { + ros2: true, + ros2-dependencies: ["std_msgs"], + timeout: 3 sec +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" +=} + +reactor InnerNode { + state id: {=const std::string=} = "inner" + timer t(0, 1 sec) + + reaction(t) {= + reactor::log::Info() << id << " reaction executes."; + std::this_thread::sleep_for(std::chrono::milliseconds(70)); + reactor::log::Info() << id << " reaction done."; + =} deadline(300 msec) {= + reactor::log::Error() << id << " deadline was violated!"; + exit(1); + =} +} + +reactor SomeNode { + state id: {=const std::string=} = "node" + + timer t(0, 2 sec) + + reaction(t) {= + reactor::log::Info() << id << " reaction executes."; + std::this_thread::sleep_for(std::chrono::milliseconds(500)); + reactor::log::Info() << id << " reaction done."; + =} deadline(300 msec) {= + reactor::log::Error() << id << " deadline was violated!"; + exit(1); + =} +} + +reactor MiddleNode { + state id: {=const std::string=} = "middle" + + @federate + inner = new InnerNode() + + timer t(0, 200 msec) + + reaction(t) {= + reactor::log::Info() << id << " reaction executes."; + std::this_thread::sleep_for(std::chrono::milliseconds(70)); + reactor::log::Info() << id << " reaction done."; + =} deadline(300 msec) {= + reactor::log::Error() << id << " deadline was violated!"; + exit(1); + =} +} + +reactor OuterNode { + state id: {=const std::string=} = "outer" + + @federate + middle = new MiddleNode() + + timer t(0, 500 msec) + + reaction(t) {= + reactor::log::Info() << id << " reaction executes."; + std::this_thread::sleep_for(std::chrono::milliseconds(200)); + reactor::log::Info() << id << " reaction done."; + =} deadline(300 msec) {= + reactor::log::Error() << id << " deadline was violated!"; + exit(1); + =} + +} + +main reactor { + outer = new OuterNode() + @federate + node = new SomeNode() +} diff --git a/test/Cpp/src/ros-federated/FederateMultipleReactions.lf b/test/Cpp/src/ros-federated/FederateMultipleReactions.lf new file mode 100644 index 0000000000..7a68c45d84 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateMultipleReactions.lf @@ -0,0 +1,88 @@ +target Cpp { + ros2: true, + timeout: 5s, + ros2-dependencies: ["std_msgs"], +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/string.hpp" +=} + +reactor Pub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + + timer t(0, 100 ms) + output out : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Pub here"); + =} + + reaction(t) -> out {= + std_msgs::msg::String msg; + msg.data = "Hello"; + out.set(msg); + =} +} + +reactor Sub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + state count: unsigned(0) + state count2: unsigned(0) + state received: bool = false + state received2: bool = false + + input in : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Sub here"); + =} + + reaction(in) {= + received = true; + auto value = in.get()->data; + reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time(); + auto expected = 100ms * count; + count++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(in) {= + received2 = true; + auto value = in.get()->data; + reactor::log::Info() << "In second reaction " << value << " at " << get_elapsed_logical_time(); + auto expected = 100ms * count2; + count2++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(shutdown) {= + if(!received || !received2) { + reactor::log::Error() << "Nothing received."; + exit(1); + } + =} +} + + +main reactor { + @federate + pub = new Pub() + @federate + sub = new Sub() + + pub.out -> sub.in +} \ No newline at end of file diff --git a/test/Cpp/src/ros-federated/FederateStringConnection.lf b/test/Cpp/src/ros-federated/FederateStringConnection.lf new file mode 100644 index 0000000000..b7d4205b19 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateStringConnection.lf @@ -0,0 +1,74 @@ +target Cpp { + ros2: true, + timeout: 5s, + ros2-dependencies: ["std_msgs"], +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/string.hpp" +=} + +reactor Pub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + + timer t(0, 100 ms) + output out : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Pub here"); + =} + + reaction(t) -> out {= + std_msgs::msg::String msg; + msg.data = "Hello"; + out.set(msg); + =} +} + +reactor Sub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + state count: unsigned(0) + state received: bool = false + + input in : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Sub here"); + =} + + reaction(in) {= + received = true; + auto value = in.get()->data; + reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time(); + auto expected = 100ms * count; + count++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "Nothing received."; + exit(1); + } + =} +} + + +main reactor { + @federate + pub = new Pub() + @federate + sub = new Sub() + + pub.out -> sub.in +} \ No newline at end of file diff --git a/test/Cpp/src/ros-federated/FederateStringConnectionDelayed.lf b/test/Cpp/src/ros-federated/FederateStringConnectionDelayed.lf new file mode 100644 index 0000000000..4839ab0e35 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateStringConnectionDelayed.lf @@ -0,0 +1,74 @@ +target Cpp { + ros2: true, + timeout: 5s, + ros2-dependencies: ["std_msgs"], +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/string.hpp" +=} + +reactor Pub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + + timer t(0, 100 ms) + output out : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Pub here"); + =} + + reaction(t) -> out {= + std_msgs::msg::String msg; + msg.data = "Hello"; + out.set(msg); + =} +} + +reactor Sub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + state count: unsigned(0) + state received: bool = false + + input in : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Sub here"); + =} + + reaction(in) {= + received = true; + auto value = in.get()->data; + reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time(); + auto expected = 100ms * count + 50ms; + count++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "Nothing received."; + exit(1); + } + =} +} + + +main reactor { + @federate + pub = new Pub() + @federate + sub = new Sub() + + pub.out -> sub.in after 50ms +} \ No newline at end of file diff --git a/test/Cpp/src/ros-federated/FederateStringConnectionPhysical.lf b/test/Cpp/src/ros-federated/FederateStringConnectionPhysical.lf new file mode 100644 index 0000000000..3b74f16a55 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateStringConnectionPhysical.lf @@ -0,0 +1,74 @@ +target Cpp { + ros2: true, + timeout: 5s, + ros2-dependencies: ["std_msgs"], +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/string.hpp" +=} + +reactor Pub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + + timer t(0, 100 ms) + output out : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Pub here"); + =} + + reaction(t) -> out {= + std_msgs::msg::String msg; + msg.data = "Hello"; + out.set(msg); + =} +} + +reactor Sub { + private preamble {= + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; + =} + state count: unsigned(0) + state received: bool = false + + input in : std_msgs::msg::String + + reaction(startup) {= + RCLCPP_INFO(lf_node->get_logger(), "Sub here"); + =} + + reaction(in) {= + received = true; + auto value = in.get()->data; + reactor::log::Info() << "Received " << value << " at " << get_elapsed_logical_time(); + auto expected = 100ms * count; + count++; + if (get_elapsed_logical_time() < expected) { + reactor::log::Error() << "Expected value not before " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "Nothing received."; + exit(1); + } + =} +} + + +main reactor { + @federate + pub = new Pub() + @federate + sub = new Sub() + + pub.out ~> sub.in +} \ No newline at end of file diff --git a/test/Cpp/src/federated/StringConnection.lf b/test/Cpp/src/ros-federated/StringConnection.lf similarity index 63% rename from test/Cpp/src/federated/StringConnection.lf rename to test/Cpp/src/ros-federated/StringConnection.lf index 2fa4b487cc..d229d67eff 100644 --- a/test/Cpp/src/federated/StringConnection.lf +++ b/test/Cpp/src/ros-federated/StringConnection.lf @@ -15,8 +15,7 @@ reactor Pub { extern rclcpp::Node* lf_node; =} - timer t(0, 500 ms) - + timer t(0, 100 ms) output out : std_msgs::msg::String reaction(startup) {= @@ -36,6 +35,7 @@ reactor Sub { extern rclcpp::Node* lf_node; =} state count: unsigned(0) + state received: bool = false input in : std_msgs::msg::String @@ -44,14 +44,28 @@ reactor Sub { =} reaction(in) {= - reactor::log::Info() << "I heard " << in.get()->data; + received = true; + auto value = in.get()->data; + reactor::log::Info() << "Received " << value; + auto expected = 100ms * count; + count++; + if (get_elapsed_logical_time() != expected) { + reactor::log::Error() << "Expected value at " << expected << " but received it at " << get_elapsed_logical_time(); + exit(1); + } + =} + + reaction(shutdown) {= + if(!received) { + reactor::log::Error() << "Nothing received."; + exit(1); + } =} } + main reactor { - @federate pub = new Pub() - @federate sub = new Sub() pub.out -> sub.in From 460f5bc57eddb5fe9f7846d4a96cb433dbd1f414 Mon Sep 17 00:00:00 2001 From: CS Date: Sat, 11 Nov 2023 15:12:50 +0100 Subject: [PATCH 11/18] refactoring; adding python parameter for exit code relay and node selection --- .../lflang/generator/cpp/CppRos2Extensions.kt | 4 +- .../lflang/generator/cpp/CppRos2Generator.kt | 2 +- .../generator/cpp/CppRos2NodeGenerator.kt | 245 +++++++++--------- .../generator/cpp/CppRos2PackageGenerator.kt | 178 ++++++++----- 4 files changed, 229 insertions(+), 200 deletions(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt index 8ae85f9252..dc0901cd4d 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Extensions.kt @@ -17,7 +17,6 @@ val Reactor.allCppMessageTypes: Set types.addAll(r.outputs.map{ROSMsgType(it.inferredType.cppType)}) for (inst in r.instantiations) reactors.add(inst.reactor) } - return types } @@ -26,6 +25,7 @@ data class ROSMsgType (private val _cppUserType : String){ val cppUserType : String get() = _cppUserType + // Transforms a ROS message type from namespaced CamelCase to snake_case for header file inclusion, e.g., "std_msgs::String" becomes "stdmsgsmsg_string_wrapped.hpp" val wrappedMsgCppInclude : String get() { // std_msgs have an extra "_" which needs to be removed @@ -36,11 +36,13 @@ data class ROSMsgType (private val _cppUserType : String){ return "#include \"lf_wrapped_msgs/msg/$msgT" } + // include for the .msg file that wraps the userType val userTypeMsgInclude : String get() { return cppUserType.replace("::", "/").replace("msg/", "") } + val wrappedCppType : String get() { return "lf_wrapped_msgs::msg::" + cppUserType.replace("::", "").replace("_", "").capitalize() + "Wrapped" diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt index 51f0482805..0b210b503d 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2Generator.kt @@ -43,7 +43,7 @@ class CppRos2Generator(generator: CppGenerator) : CppPlatformGenerator(generator for (nodeGen in nodeGenerators) { rosMsgTypes.addAll(nodeGen.reactor.allCppMessageTypes) } - + // generate wrapped messages val msgWrapGen = CppRos2MessageWrapperGenerator(rosMsgTypes) for ((messageFileName, messageFileContent) in msgWrapGen.generateMessageFiles()) { FileUtil.writeToFile(messageFileContent, fileConfig.srcGenBasePath.resolve("lf_wrapped_msgs").resolve("msg").resolve(messageFileName)) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt index 951f6b030a..db32f69e2b 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2NodeGenerator.kt @@ -14,8 +14,9 @@ class CppRos2NodeGenerator( private var subReactorEndpointDeclarations : String = "" private var subReactorEndpointInitializers : String = "" - init { - // generating endPoint declarations and initializers for non-federate-childs of this federate + + // generating endPoint declarations and initializers for non-federate-childs of this federate + private fun generateSubReactorCode() { // recursively searching through all reactors of this federate to add the corresponding endpoints if they are in a federate connection // maybe TODO: resolve multiports banks etc val todo: MutableList>> = mutableListOf(Triple("", reactor, listOf())) @@ -25,153 +26,143 @@ class CppRos2NodeGenerator( for (inst in r.instantiations) { if (!AttributeUtils.isFederate(inst)) todo.add(Triple(prefix + "/" + inst.name, inst.reactor, preInst + inst)) } - for (con in r.connections) { - for (index in con.leftPorts.indices) { - val l : VarRef = con.leftPorts[index] - val r : VarRef = con.rightPorts[index] - if (l.container != null && !AttributeUtils.isFederate(l.container) - && r.container != null && AttributeUtils.isFederate(r.container)) { - val lPort = l.variable as Output - var lPortVarName = prefix + l.container.name + "_" + lPort.name - var topic_name = prefix + l.container.name + "/" + lPort.name - if (con.isPhysical) { - lPortVarName += "_physical" - subReactorEndpointDeclarations += System.lineSeparator() + - "| std::unique_ptr> $lPortVarName;" - subReactorEndpointInitializers += """ - | $lPortVarName = std::make_unique>(lf_federate_prefix + "/$topic_name"); - | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor physical endpoint $lPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); - """ - } - else { - subReactorEndpointDeclarations += System.lineSeparator() + - "| std::unique_ptr> $lPortVarName;" + for (con in r.connections) + processSubReactorCon(prefix, con, preInst) + } + } - subReactorEndpointInitializers += """ - | $lPortVarName = std::make_unique>(lf_federate_prefix + "/$topic_name"); - | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor endpoint $lPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); - """ - } + private fun createPubSubUniquePtrDecl(isPub: Boolean, isPhysical: Boolean, userType: String, wrappedType: String, varName: String): String { + var decl : String = "std::unique_ptrreactors()) + private fun createEndPointDeclInit(isPub: Boolean, isPhysical: Boolean, delay: Expression?, userType: String, wrappedType: String, varName: String, topicName: String, debugMsg: Boolean = true) { + var newVarName : String = varName + if (isPhysical) newVarName += "_physical" + subReactorEndpointDeclarations += System.lineSeparator() + + "| ${createPubSubUniquePtrDecl(isPub, isPhysical, + userType, + wrappedType, + newVarName)}" + + subReactorEndpointInitializers += System.lineSeparator() + + "| ${createPubSubUniquePtrInit(isPub, + isPhysical, + delay, userType, + wrappedType, + newVarName, + topicName)}" + + if (debugMsg) + subReactorEndpointInitializers += System.lineSeparator() + + "| RCLCPP_DEBUG_STREAM(this->get_logger(), \"subreactor endpoint $newVarName got topic \"+ lf_federate_prefix + \"/$topicName\");" + } + + private fun createEndpointPortBindingCode(isPub: Boolean, isPhysical: Boolean, varName: String, preInst: List, container: Instantiation, portName: String) { + var newVarName : String = varName + if (isPhysical) newVarName += "_physical" + subReactorEndpointInitializers += """ + | reactor::Reactor* ${newVarName}_reactor = lf_reactor.get(); + | bool ${newVarName}_subreactor_found; + | ${(preInst + container).joinToString(separator = System.lineSeparator()) { + """ + | ${newVarName}_subreactor_found = false; + | for(auto r : ${newVarName}_reactor->reactors()) | if (r->name() == "${it.name}") { - | ${lPortVarName}_subreactor_found = true; - | ${lPortVarName}_reactor = r; + | ${newVarName}_subreactor_found = true; + | ${newVarName}_reactor = r; | } - | if (!${lPortVarName}_subreactor_found) + | if (!${newVarName}_subreactor_found) | RCLCPP_ERROR(this->get_logger(), "Failed to find subreactor \"${it.name}\""); """ - } - } - | $lPortVarName->set_port(&dynamic_cast<${l.container.reactor.name}*>(${lPortVarName}_reactor)->${lPort.name}); - """ - } - if (r.container != null && !AttributeUtils.isFederate(r.container) - && l.container != null && AttributeUtils.isFederate(l.container)) { - val rPort = r.variable as Input - var rPortVarName = prefix + r.container.name + "_" + rPort.name - var topic_name= prefix + l.container.name + "/" + (l.variable as Port).name - // if the container is a federate where the out port is remapped check until we find one that should not be remapped - if (AttributeUtils.isFederate(l.container)) { - topic_name = prefix - var prev_l = l - while(AttributeUtils.isFederate(prev_l.container)) { - topic_name += prev_l.container.name - var next_l : VarRef? = null; - for (fed_con in prev_l.container.reactor.connections) { - for ((fed_con_index, fed_con_rPort) in fed_con.rightPorts.withIndex()) { - if (fed_con_rPort.name == (prev_l.variable as Port).name && fed_con_rPort.container == null) - next_l = fed_con.leftPorts[fed_con_index] - } - } - if (next_l == null) { - topic_name += "/" +(prev_l.variable as Port).name - break - } - else { - topic_name += "/" - prev_l = next_l - next_l = null } - } } + | $newVarName->${if (isPub) "set_port" else "add_port"}(&dynamic_cast<${container.reactor.name}*>(${newVarName}_reactor)->$portName); + """ + } - if (con.isPhysical) { - rPortVarName += "_physical" - subReactorEndpointDeclarations += System.lineSeparator() + - "| std::unique_ptr> $rPortVarName;" - subReactorEndpointInitializers += """ - | $rPortVarName = std::make_unique> - | (lf_federate_prefix + "/$topic_name","${rPortVarName}_sub", lf_env.get() - | ${if (con.delay != null) ", " + con.delay.toCppTime() else ""}); - | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor physical endpoint $rPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); - """ - } - else { - subReactorEndpointDeclarations += System.lineSeparator() + - "| std::unique_ptr> $rPortVarName;" - subReactorEndpointInitializers += """ - | $rPortVarName = std::make_unique> - | (lf_federate_prefix + "/$topic_name", "${rPortVarName}_sub",lf_env.get() - | ${if (con.delay != null) ", " + con.delay.toCppTime() else ""}); - | RCLCPP_DEBUG_STREAM(this->get_logger(), "subreactor endpoint $rPortVarName got topic "+ lf_federate_prefix + "/$topic_name"); - """ - } + private fun processSubReactorCon(prefix: String, con: Connection, preInst: List) { + for ((l, r) in con.leftPorts.zip(con.rightPorts)){ + if (l.container != null && !AttributeUtils.isFederate(l.container) + && r.container != null && AttributeUtils.isFederate(r.container)) { + val lPort = l.variable as Output + val lPortVarName = prefix + l.container.name + "_" + lPort.name + val topicName = prefix + l.container.name + "/" + lPort.name - subReactorEndpointInitializers += """ - | reactor::Reactor* ${rPortVarName}_reactor = lf_reactor.get(); - | bool ${rPortVarName}_subreactor_found; - | ${(preInst + r.container).joinToString(separator = System.lineSeparator()) { - """ - | ${rPortVarName}_subreactor_found = false; - | for(auto r : ${rPortVarName}_reactor->reactors()) - | if (r->name() == "${it.name}") { - | ${rPortVarName}_subreactor_found = true; - | ${rPortVarName}_reactor = r; - | } - | if (!${rPortVarName}_subreactor_found) - | RCLCPP_ERROR(this->get_logger(), "Failed to find subreactor \"${it.name}\""); - """ + createEndPointDeclInit(true, con.isPhysical, null, + lPort.inferredType.cppType, + ROSMsgType(lPort.inferredType.cppType).wrappedCppType, + lPortVarName, + "/$topicName") + + createEndpointPortBindingCode(true, con.isPhysical, lPortVarName, preInst, l.container, lPort.name) + } + if (r.container != null && !AttributeUtils.isFederate(r.container) + && l.container != null && AttributeUtils.isFederate(l.container)) { + val rPort = r.variable as Input + val rPortVarName = prefix + r.container.name + "_" + rPort.name + var topicName= prefix + l.container.name + "/" + (l.variable as Port).name + // if the container is a federate where the out port is remapped check until we find one that should not be remapped + if (AttributeUtils.isFederate(l.container)) { + topicName = prefix + var prev_l = l + while(true) { + topicName += prev_l.container.name + var next_l : VarRef? = null; + for (fed_con in prev_l.container.reactor.connections) { + for ((fed_con_index, fed_con_rPort) in fed_con.rightPorts.withIndex()) { + if (fed_con_rPort.name == (prev_l.variable as Port).name && fed_con_rPort.container == null) + next_l = fed_con.leftPorts[fed_con_index] + } } + if (next_l == null) { + topicName += "/" +(prev_l.variable as Port).name + break + } + else { + topicName += "/" + prev_l = next_l + next_l = null } - | $rPortVarName->add_port(&dynamic_cast<${r.container.reactor.name}*>(${rPortVarName}_reactor)->${rPort.name}); - """ } - } - } + createEndPointDeclInit(false, con.isPhysical, con.delay, + rPort.inferredType.cppType, + ROSMsgType(rPort.inferredType.cppType).wrappedCppType, + rPortVarName, + "/$topicName") + createEndpointPortBindingCode(false, con.isPhysical, rPortVarName, preInst, r.container, rPort.name) + } } } + init { + generateSubReactorCode() + } private fun generateEndpointDeclarations(): String { diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index eb9daa5dc7..fe3240297b 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -120,7 +120,7 @@ class CppRos2PackageGenerator(generator: CppGenerator) { } } } - + // Generates a Python code string that represents the structure of a given Reactor object, detailing its instantiations and connections private fun createReactorStructurePython(reactor : Reactor, nodeName : String = "") : String { var s = "Reactor(\"$nodeName\"" s += "," + System.lineSeparator() @@ -179,10 +179,24 @@ class CppRos2PackageGenerator(generator: CppGenerator) { val reactorStructurePython = createReactorStructurePython(mainReactorNodeGen.reactor) return """ |from __future__ import annotations - |from typing import List, Tuple, Dict, Optional + |from typing import List, Tuple, Dict, Optional, Deque |from dataclasses import dataclass - |from launch import LaunchDescription + |from collections import deque + |from launch import LaunchContext, LaunchDescription |from launch_ros.actions import Node + |from launch.actions import OpaqueFunction, DeclareLaunchArgument + |from launch.substitutions import LocalSubstitution, LaunchConfiguration + |from pprint import pprint as prettyprint + | + |# if a node fails (exit code != 0) the launch file is exited with the same exit code + |# this got implemented to allow LF tests with ROS2 launch files + |def relay_node_fail_to_launch(context: LaunchContext): + | assert(isinstance(context, LaunchContext)) + | # event is instance of launch.events.process.ProcessExited + | return_code = LocalSubstitution("event.returncode").perform(context) + | assert(isinstance(return_code, int)) + | if return_code != 0: + | exit(return_code) | |class Port: | instance_name: str @@ -193,13 +207,12 @@ class CppRos2PackageGenerator(generator: CppGenerator) { | self.instance_name = _inst_name | self.name = _port_name | self.is_input = _is_input - | | |class Connection: | leftPorts: List[Port] | rightPorts: List[Port] | physical: bool - | delay: Optional[long] + | delay: Optional[int] | | def __init__(self, lPorts, rPorts, isPhysical, _delay): | self.leftPorts = lPorts @@ -231,90 +244,113 @@ class CppRos2PackageGenerator(generator: CppGenerator) { |$reactorStructurePython |) | - |def get_instance_by_prefix(prefix: str) -> Instantiation: - | if prefix[:len("/${mainReactorNodeGen.reactor.name}")] != "/${mainReactorNodeGen.reactor.name}": - | raise RuntimeError("prefix must start with /${mainReactorNodeGen.reactor.name}") - | prefix_without_main = prefix[len("/${mainReactorNodeGen.reactor.name}"):] - | splits = prefix_without_main.split("/") - | if not splits: - | return mainInstance - | inst = mainInstance - | if splits[:1][0] != "": - | raise RuntimeError("prefix is incorrect") - | for split in splits: - | filter_res = filter(lambda x: x.name == split, inst.reactor.instantiations) - | if len(filter_res) != 1: - | raise RuntimeError("instance " + split +" not found") - | else: - | inst = filter_res[0] - | return inst - | + |NODE_SELECTION_LIST_ARG_NAME = "node_selection_list" + |ALL_NODES_INDICATOR = "__all__" # Special value to indicate all nodes should be launched + |RELAY_FAIL_ARG_NAME = "relay_node_fail_to_launch" + | | |def generate_launch_description(): - | + | # Declare launch argument for node list + | node_selection_list_arg = DeclareLaunchArgument( + | NODE_SELECTION_LIST_ARG_NAME, default_value=ALL_NODES_INDICATOR, + | description='Comma-separated list of nodes to launch based on their lf federate prefix (if not specified, all nodes will be launched)' + | ) + | + | # Declare launch argument for exit on node fail + | relay_fail_arg = DeclareLaunchArgument( + | RELAY_FAIL_ARG_NAME, default_value="True", + | description='If true, when a node exits with an exit code other than 0 the launch will exit with the same code.' + | ) + | + | # this dict accumulates parameters for each node based on the connections (of the containing node) | prefix_param_dict : Dict[str, Dict[str,str]] = {} - | container_prefix_dict : Dict[str, str] = {} # key: instance prefix, # value: container prefix - | instances_todo : List[Tuple[str, Instantiation, List[Connection]]] = [["", mainInstance, []]] # str is container prefix, list is connections from container regarding this instance + | # str is container prefix, list is connections from container regarding this instance + | # instances are processed in a breath-first manner + | # (to make sure that the respective parent has already been processed for each instance) + | instances_todo : Deque[Tuple[str, Instantiation, List[Connection]]] = deque([("", mainInstance, [])]) + | | while instances_todo: - | [prefix, instance, connections] = instances_todo.pop(0) + | prefix, instance, connections = instances_todo.popleft() | lf_federate_prefix = prefix + "/" + instance.name - | # add next instances to list + | + | # add child instances to queue for later processing | for next_inst in instance.reactor.instantiations: - | next_conns = [] - | for con in instance.reactor.connections: - | for i in range(len(con.rightPorts)): - | if con.leftPorts[i].instance_name == next_inst.name or con.rightPorts[i].instance_name == next_inst.name: - | next_conns.append(con) + | # filter connections that concern the child instance + | next_conns = [con for con in instance.reactor.connections if + | any(p.instance_name == next_inst.name for p in con.leftPorts + con.rightPorts)] | instances_todo.append((lf_federate_prefix, next_inst, next_conns)) - | # create node parameters | - | container_prefix_dict[lf_federate_prefix] = prefix - | prefix_param_dict[lf_federate_prefix] = instance_param_dict = {} - | instance_param_dict["lf_federate_prefix"] = lf_federate_prefix - | for con in connections: - | physical_string = "_physical" if con.physical else "" - | for i in range(len(con.rightPorts)): - | leftP = con.leftPorts[i] - | rightP = con.rightPorts[i] - | if leftP.instance_name == instance.name and not leftP.is_input: - | instance_param_dict[leftP.name + physical_string] = lf_federate_prefix + "/" + leftP.name - | if rightP.instance_name == instance.name and leftP.is_input and leftP.instance_name == None: - | if leftP.name in prefix_param_dict[prefix]: - | instance_param_dict[rightP.name] = prefix_param_dict[prefix][leftP.name] - | if leftP.name + "_physical" in prefix_param_dict[prefix]: - | instance_param_dict[rightP.name+"_physical"] = prefix_param_dict[prefix][leftP.name+"_physical"] - | if rightP.instance_name == instance.name and rightP.is_input and leftP.instance_name != None: - | instance_param_dict[rightP.name + physical_string] = prefix +"/"+ leftP.instance_name + "/" + leftP.name - | if con.delay is not None: - | instance_param_dict[rightP.name + physical_string + "_delay"] = con.delay - | if leftP.instance_name == instance.name and rightP.instance_name == None and not rightP.is_input: - | # connection like r{x.port -> out} while current instance is x - | # we go through the reactors to see if parent.out is used to replace it with x.port - | name_to_search_for = prefix + "/" + rightP.name - | print(name_to_search_for) - | for key in prefix_param_dict: - | for key2 in prefix_param_dict[key]: - | if prefix_param_dict[key][key2] == name_to_search_for: - | prefix_param_dict[key][key2] = lf_federate_prefix + "/" + leftP.name - + | # create node parameters + | generate_instance_parameters(lf_federate_prefix, prefix, instance, connections, prefix_param_dict) | + | return LaunchDescription([node_selection_list_arg, + | OpaqueFunction(function=create_node_launch_descriptions, args=[prefix_param_dict])]) | - | # launch nodes if they are federate - | to_search_feds : List[Tuple[str,Instantiation]] = [["", mainInstance]] + |def create_node_launch_descriptions(context, prefix_param_dict) -> List[Node]: + | # launch nodes if they are federate (have an executable) with parameters from prefix_param_dict + | to_search_feds : Deque[Tuple[str,Instantiation]] = deque([("", mainInstance)]) + | node_selection_list = LaunchConfiguration(NODE_SELECTION_LIST_ARG_NAME).perform(context) + | relay_node_fail = LaunchConfiguration(RELAY_FAIL_ARG_NAME).perform(context) + | # dict is use to pass parameter conditionally + | relay_node_fail_dict = dict(on_exit=OpaqueFunction(function=relay_node_fail_to_launch)) + | if relay_node_fail.lower() in ['0', 'false']: + | relay_node_fail_dict = {} + | launch_all_nodes = False + | if node_selection_list == ALL_NODES_INDICATOR: + | launch_all_nodes = True + | else: + | node_selection_list = node_selection_list.split(",") | nodes = [] | while to_search_feds: - | [prefix, instance] = to_search_feds.pop(0) + | [prefix, instance] = to_search_feds.popleft() | fed_prefix = prefix + "/" + instance.name | for inst in instance.reactor.instantiations: | to_search_feds.append((fed_prefix, inst)) | if instance.executable != None: - | nodes.append(Node(package='${fileConfig.name}', + | if launch_all_nodes or fed_prefix in node_selection_list: + | nodes.append(Node(package='${fileConfig.name}', | executable=instance.executable, | name=fed_prefix.replace("/","_"), - | parameters=[prefix_param_dict[fed_prefix]]) - | ) - | print("parameters for node with prefix " + fed_prefix, prefix_param_dict[fed_prefix]) - | return LaunchDescription(nodes) + | parameters=[prefix_param_dict[fed_prefix]], + | **relay_node_fail_dict + | ) + | ) + | print("launching node with prefix " + fed_prefix) + | prettyprint(prefix_param_dict[fed_prefix]) + | return nodes + | + |def generate_instance_parameters(lf_federate_prefix: str, prefix: str, instance: Instantiation, connections: List[Connection], prefix_param_dict): + | param_dict = {"lf_federate_prefix": lf_federate_prefix} + | for con in connections: + | update_param_dict_for_con(con, instance, lf_federate_prefix, prefix, param_dict, prefix_param_dict) + | prefix_param_dict[lf_federate_prefix] = param_dict + | + | + |def update_param_dict_for_con(con, instance, lf_federate_prefix, prefix, instance_param_dict, prefix_param_dict): + | # con is from parent concerning the instance + | physical_string = "_physical" if con.physical else "" + | for leftP, rightP in zip(con.leftPorts, con.rightPorts): + | if leftP.instance_name == instance.name and not leftP.is_input: + | instance_param_dict[leftP.name + physical_string] = lf_federate_prefix + "/" + leftP.name + | if rightP.instance_name == instance.name and leftP.is_input and leftP.instance_name == None: + | if leftP.name in prefix_param_dict[prefix]: + | instance_param_dict[rightP.name] = prefix_param_dict[prefix][leftP.name] + | if leftP.name + "_physical" in prefix_param_dict[prefix]: + | instance_param_dict[rightP.name+"_physical"] = prefix_param_dict[prefix][leftP.name+"_physical"] + | if rightP.instance_name == instance.name and rightP.is_input and leftP.instance_name != None: + | instance_param_dict[rightP.name + physical_string] = prefix +"/"+ leftP.instance_name + "/" + leftP.name + | if con.delay is not None: + | instance_param_dict[rightP.name + physical_string + "_delay"] = con.delay + | if leftP.instance_name == instance.name and rightP.instance_name == None and not rightP.is_input: + | # connection like r{x.port -> out} while current instance is x + | # we go through the reactors to see if parent.out is used to replace it with x.port + | name_to_search_for = prefix + "/" + rightP.name + | print(name_to_search_for) + | for key in prefix_param_dict: + | for key2 in prefix_param_dict[key]: + | if prefix_param_dict[key][key2] == name_to_search_for: + | prefix_param_dict[key][key2] = lf_federate_prefix + "/" + leftP.name + | """.trimMargin() } From 129a721f5c0805b2b06e88cfc496ab6dec44f073 Mon Sep 17 00:00:00 2001 From: CS Date: Sat, 11 Nov 2023 15:16:00 +0100 Subject: [PATCH 12/18] test update --- .../FederateHierarchicalConnections2.lf | 31 ++--- .../FederateHierarchicalConnections2relay.lf | 93 ++++++++++++++ .../FederateHierarchicalConnections3.lf | 114 ++++++++++++++++++ 3 files changed, 217 insertions(+), 21 deletions(-) create mode 100644 test/Cpp/src/ros-federated/FederateHierarchicalConnections2relay.lf create mode 100644 test/Cpp/src/ros-federated/FederateHierarchicalConnections3.lf diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf index eafac73be1..a5515961aa 100644 --- a/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf +++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2.lf @@ -2,7 +2,7 @@ target Cpp { ros2: true, ros2-dependencies: ["std_msgs"], - timeout: 300 sec + timeout: 3 sec } public preamble {= @@ -55,43 +55,32 @@ reactor Print { reactor GainContainer { input in_cont: std_msgs::msg::Int64 output out_cont: std_msgs::msg::Int64 - output out2: std_msgs::msg::Int64 - output out3: std_msgs::msg::Int64 - // own port to federate and other way around not working yet - @federate + @federate gain = new Gain() in_cont -> gain.in reaction(in_cont) {= //std_msgs::msg::Int64 i; - RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container gaining"); + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container reacting"); //i.data = in.get()->data*2; - //gain.in.set(i); + // this does not work since gain instance does not exist inside of GainContainer if it is a federate + // could be implemented with stubs or use relays (see FederateHierarchicalConnections3) + // However, it will still not work until zero delay loop problem is solved + //gain.in.set(i); =} - //reaction(gain.out) {= - // RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container reacting"); - //=} gain.out -> out_cont - //gain.out -> out2 } main reactor { source = new Source() - // lf says no when referencing anything except ports from instances - //reaction(source.t){= - // RCLCPP_INFO_STREAM(lf_node->get_logger(), "main reacting to source.t"); - //=} - - @federate + @federate container = new GainContainer() print = new Print() - print2 = new Print() - source.out ~> container.in_cont - container.out_cont -> print.in - container.out_cont -> print2.in after 2s + source.out -> container.in_cont + container.out_cont -> print.in after 1s } diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections2relay.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2relay.lf new file mode 100644 index 0000000000..d89747b01d --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections2relay.lf @@ -0,0 +1,93 @@ +// Test data transport across hierarchy. +// this test microstep overflows (until zero delay loops problem is solved) +target Cpp { + ros2: true, + ros2-dependencies: ["std_msgs"], + timeout: 3 sec +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/int64.hpp" + + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; +=} + +reactor Source { + output out: std_msgs::msg::Int64 + timer t + + reaction(t) -> out {= + RCLCPP_INFO_STREAM(lf_node->get_logger(), "source sending"); + std_msgs::msg::Int64 i; + i.data = 1; + out.set(i); + =} +} + +reactor Gain { + input in: std_msgs::msg::Int64 + output out: std_msgs::msg::Int64 + + reaction(in) -> out {= + std_msgs::msg::Int64 i; + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain gaining"); + i.data = in.get()->data*2; + out.set(i); + =} +} + +reactor Relay { + input in: std_msgs::msg::Int64 + output out: std_msgs::msg::Int64 + + reaction(in) -> out {= + out.set(in.get()); + =} +} + +reactor Print { + input in: std_msgs::msg::Int64 + + reaction(in) {= + std_msgs::msg::Int64 i = *in.get(); + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data); + if (i.data != 4) { + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 4"); + exit(1); + } + =} +} + +reactor GainContainer { + input in_cont: std_msgs::msg::Int64 + output out_cont: std_msgs::msg::Int64 + + @federate + gain = new Gain() + relay = new Relay() + + + reaction(in_cont) -> relay.in {= + std_msgs::msg::Int64 i; + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container gaining"); + i.data = in_cont.get()->data*2; + relay.in.set(i); + =} + relay.out -> gain.in + gain.out -> out_cont +} + +main reactor { + source = new Source() + + + @federate + container = new GainContainer() + + print = new Print() + + source.out -> container.in_cont + container.out_cont -> print.in after 1s +} diff --git a/test/Cpp/src/ros-federated/FederateHierarchicalConnections3.lf b/test/Cpp/src/ros-federated/FederateHierarchicalConnections3.lf new file mode 100644 index 0000000000..83da759432 --- /dev/null +++ b/test/Cpp/src/ros-federated/FederateHierarchicalConnections3.lf @@ -0,0 +1,114 @@ +// Test data transport across hierarchy. +// this test microstep overflows (until zero delay loops problem is solved) +target Cpp { + ros2: true, + ros2-dependencies: ["std_msgs"], + timeout: 3 sec +} + +public preamble {= + #include "rclcpp/rclcpp.hpp" + #include "std_msgs/msg/int64.hpp" + + // FIXME: forward declaration to make the node visible + extern rclcpp::Node* lf_node; +=} + +reactor Source { + output out: std_msgs::msg::Int64 + timer t + + reaction(t) -> out {= + RCLCPP_INFO_STREAM(lf_node->get_logger(), "source sending"); + std_msgs::msg::Int64 i; + i.data = 1; + out.set(i); + =} +} + +reactor Gain { + input in: std_msgs::msg::Int64 + output out: std_msgs::msg::Int64 + + reaction(in) -> out {= + std_msgs::msg::Int64 i; + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain gaining"); + i.data = in.get()->data*2; + out.set(i); + =} +} + +reactor Relay { + input in: std_msgs::msg::Int64 + output out: std_msgs::msg::Int64 + + reaction(in) -> out {= + out.set(in.get()); + =} +} + +reactor Print { + input in: std_msgs::msg::Int64 + + reaction(in) {= + std_msgs::msg::Int64 i = *in.get(); + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Received: " << i.data); + if (i.data != 8) { + RCLCPP_INFO_STREAM(lf_node->get_logger(), "Expected 8"); + exit(1); + } + =} +} + +reactor GainContainerContainer { + input in_cont: std_msgs::msg::Int64 + output out_cont: std_msgs::msg::Int64 + + @federate + gaincont = new GainContainer() + relay = new Relay() + + reaction(in_cont) -> relay.in {= + std_msgs::msg::Int64 i; + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain containercontainer gaining"); + i.data = in_cont.get()->data*2; + relay.in.set(i); + =} + + relay.out -> gaincont.in_cont + gaincont.out_cont -> out_cont +} + + + +reactor GainContainer { + input in_cont: std_msgs::msg::Int64 + output out_cont: std_msgs::msg::Int64 + + @federate + gain = new Gain() + relay = new Relay() + + + reaction(in_cont) -> relay.in {= + std_msgs::msg::Int64 i; + RCLCPP_INFO_STREAM(lf_node->get_logger(), "gain container gaining"); + i.data = in_cont.get()->data*2; + relay.in.set(i); + =} + relay.out -> gain.in + gain.out -> out_cont +} + +main reactor { + source = new Source() + + + @federate + container = new GainContainerContainer() + + print = new Print() + + source.out -> container.in_cont + container.out_cont -> print.in after 1s +} From 1dc1ccc93cabe21f8ea461c3dab2895020fd6550 Mon Sep 17 00:00:00 2001 From: CS Date: Sat, 11 Nov 2023 15:19:28 +0100 Subject: [PATCH 13/18] small launch arg fix --- .../kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt index fe3240297b..530c005619 100644 --- a/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt +++ b/core/src/main/kotlin/org/lflang/generator/cpp/CppRos2PackageGenerator.kt @@ -283,7 +283,7 @@ class CppRos2PackageGenerator(generator: CppGenerator) { | # create node parameters | generate_instance_parameters(lf_federate_prefix, prefix, instance, connections, prefix_param_dict) | - | return LaunchDescription([node_selection_list_arg, + | return LaunchDescription([node_selection_list_arg, relay_fail_arg, | OpaqueFunction(function=create_node_launch_descriptions, args=[prefix_param_dict])]) | |def create_node_launch_descriptions(context, prefix_param_dict) -> List[Node]: From cb94d6d865d4a151040a449aaaa98493d124f861 Mon Sep 17 00:00:00 2001 From: CS Date: Sun, 12 Nov 2023 17:52:56 +0100 Subject: [PATCH 14/18] remove test log folder --- .idea/codeStyles/Project.xml | 3 - core/src/main/resources/lib/cpp/reactor-cpp | 2 +- test/Cpp/log/COLCON_IGNORE | 0 .../VoidConnection/command.log | 4 - .../VoidConnection/stderr.log | 37 -- .../VoidConnection/stdout.log | 41 -- .../VoidConnection/stdout_stderr.log | 78 ---- .../VoidConnection/streams.log | 82 ---- .../log/build_2023-07-02_12-11-59/events.log | 228 ------------ .../build_2023-07-02_12-11-59/logger_all.log | 342 ----------------- .../reactor-cpp-default/command.log | 6 - .../reactor-cpp-default/stderr.log | 6 - .../reactor-cpp-default/stdout.log | 64 ---- .../reactor-cpp-default/stdout_stderr.log | 70 ---- .../reactor-cpp-default/streams.log | 76 ---- .../StringConnection/command.log | 4 - .../StringConnection/stderr.log | 38 -- .../StringConnection/stdout.log | 41 -- .../StringConnection/stdout_stderr.log | 79 ---- .../StringConnection/streams.log | 83 ----- .../log/build_2023-07-02_12-13-14/events.log | 171 --------- .../build_2023-07-02_12-13-14/logger_all.log | 351 ------------------ .../reactor-cpp-default/command.log | 4 - .../reactor-cpp-default/stderr.log | 0 .../reactor-cpp-default/stdout.log | 36 -- .../reactor-cpp-default/stdout_stderr.log | 36 -- .../reactor-cpp-default/streams.log | 40 -- test/Cpp/log/latest | 1 - test/Cpp/log/latest_build | 1 - 29 files changed, 1 insertion(+), 1923 deletions(-) delete mode 100644 test/Cpp/log/COLCON_IGNORE delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/VoidConnection/command.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/VoidConnection/stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/VoidConnection/stdout.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/VoidConnection/stdout_stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/VoidConnection/streams.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/events.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/logger_all.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/reactor-cpp-default/command.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/reactor-cpp-default/stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/reactor-cpp-default/stdout.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/reactor-cpp-default/stdout_stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-11-59/reactor-cpp-default/streams.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/StringConnection/command.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/StringConnection/stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/StringConnection/stdout.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/StringConnection/stdout_stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/StringConnection/streams.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/events.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/logger_all.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/reactor-cpp-default/command.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/reactor-cpp-default/stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/reactor-cpp-default/stdout.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/reactor-cpp-default/stdout_stderr.log delete mode 100644 test/Cpp/log/build_2023-07-02_12-13-14/reactor-cpp-default/streams.log delete mode 120000 test/Cpp/log/latest delete mode 120000 test/Cpp/log/latest_build diff --git a/.idea/codeStyles/Project.xml b/.idea/codeStyles/Project.xml index 8342d9bfe1..349ead0088 100644 --- a/.idea/codeStyles/Project.xml +++ b/.idea/codeStyles/Project.xml @@ -61,9 +61,6 @@