From 802664d878620cfa3cab29b6dc3f550ff8f9be2d Mon Sep 17 00:00:00 2001 From: Ivan Santiago Paunovic Date: Mon, 24 Aug 2020 18:53:03 -0300 Subject: [PATCH 1/3] Update qos profile (osrf/ros2_java#20) Signed-off-by: Ivan Santiago Paunovic --- rcljava/CMakeLists.txt | 3 + rcljava/include/org_ros2_rcljava_RCLJava.h | 3 +- .../src/main/cpp/org_ros2_rcljava_RCLJava.cpp | 35 ++++- .../main/java/org/ros2/rcljava/RCLJava.java | 22 +-- .../java/org/ros2/rcljava/qos/QoSProfile.java | 139 +++++++++++++++--- .../ros2/rcljava/qos/policies/Liveliness.java | 32 ++++ .../org/ros2/rcljava/qos/QoSProfileTest.java | 73 +++++++++ 7 files changed, 274 insertions(+), 33 deletions(-) create mode 100644 rcljava/src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java create mode 100644 rcljava/src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java diff --git a/rcljava/CMakeLists.txt b/rcljava/CMakeLists.txt index 3cbe0318..366c7f77 100644 --- a/rcljava/CMakeLists.txt +++ b/rcljava/CMakeLists.txt @@ -153,6 +153,7 @@ set(${PROJECT_NAME}_sources "src/main/java/org/ros2/rcljava/publisher/PublisherImpl.java" "src/main/java/org/ros2/rcljava/qos/policies/Durability.java" "src/main/java/org/ros2/rcljava/qos/policies/History.java" + "src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java" "src/main/java/org/ros2/rcljava/qos/policies/QoSPolicy.java" "src/main/java/org/ros2/rcljava/qos/policies/Reliability.java" "src/main/java/org/ros2/rcljava/qos/QoSProfile.java" @@ -237,6 +238,7 @@ if(BUILD_TESTING) # "src/test/java/org/ros2/rcljava/parameters/AsyncParametersClientTest.java" # "src/test/java/org/ros2/rcljava/parameters/SyncParametersClientTest.java" "src/test/java/org/ros2/rcljava/publisher/PublisherTest.java" + "src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java" "src/test/java/org/ros2/rcljava/subscription/SubscriptionTest.java" "src/test/java/org/ros2/rcljava/timer/TimerTest.java" ) @@ -252,6 +254,7 @@ if(BUILD_TESTING) "org.ros2.rcljava.node.NodeTest" # "org.ros2.rcljava.parameters.SyncParametersClientTest" "org.ros2.rcljava.publisher.PublisherTest" + "org.ros2.rcljava.qos.QoSProfileTest" "org.ros2.rcljava.subscription.SubscriptionTest" "org.ros2.rcljava.timer.TimerTest" ) diff --git a/rcljava/include/org_ros2_rcljava_RCLJava.h b/rcljava/include/org_ros2_rcljava_RCLJava.h index 4d8d1b50..85859eb7 100644 --- a/rcljava/include/org_ros2_rcljava_RCLJava.h +++ b/rcljava/include/org_ros2_rcljava_RCLJava.h @@ -52,7 +52,8 @@ JNICALL Java_org_ros2_rcljava_RCLJava_nativeGetRMWIdentifier(JNIEnv *, jclass); */ JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeConvertQoSProfileToHandle( - JNIEnv *, jclass, jint, jint, jint, jint, jboolean); + JNIEnv *, jclass, + jint, jint, jint, jint, jlong, jint, jlong, jint, jint, jlong, jint, jboolean); /* * Class: org_ros2_rcljava_RCLJava diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp index 1a3b640d..957329d9 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_RCLJava.cpp @@ -31,6 +31,7 @@ #include "org_ros2_rcljava_RCLJava.h" +using rcljava_common::exceptions::rcljava_throw_exception; using rcljava_common::exceptions::rcljava_throw_rclexception; using rcljava_common::signatures::convert_from_java_signature; using rcljava_common::signatures::convert_to_java_signature; @@ -152,9 +153,31 @@ Java_org_ros2_rcljava_RCLJava_nativeGetRMWIdentifier(JNIEnv * env, jclass) return env->NewStringUTF(rmw_implementation_identifier); } +#define RCLJAVA_QOS_SET_RMW_TIME(qos_profile, policy_name, seconds, nanos) \ + do { \ + if (seconds < 0) { \ + rcljava_throw_exception( \ + env, "java/lang/IllegalArgumentException", \ + "seconds must not be negative for " #policy_name); \ + free(qos_profile); \ + return 0; \ + } \ + if (nanos < 0) { \ + rcljava_throw_exception( \ + env, "java/lang/IllegalArgumentException", \ + "nanoseconds must not be negative for " #policy_name); \ + free(qos_profile); \ + return 0; \ + } \ + qos_profile->policy_name.sec = static_cast(seconds); \ + qos_profile->policy_name.nsec = static_cast(nanos); \ + } while (0) + JNIEXPORT jlong JNICALL Java_org_ros2_rcljava_RCLJava_nativeConvertQoSProfileToHandle( - JNIEnv *, jclass, jint history, jint depth, jint reliability, jint durability, + JNIEnv * env, jclass, jint history, jint depth, jint reliability, jint durability, + jlong deadline_sec, jint deadline_nanos, jlong lifespan_sec, jint lifespan_nanos, + jint liveliness, jlong liveliness_lease_sec, jint liveliness_lease_nanos, jboolean avoidROSNamespaceConventions) { rmw_qos_profile_t * qos_profile = @@ -163,11 +186,11 @@ Java_org_ros2_rcljava_RCLJava_nativeConvertQoSProfileToHandle( qos_profile->depth = depth; qos_profile->reliability = static_cast(reliability); qos_profile->durability = static_cast(durability); - // TODO(jacobperron): Expose deadline, lifespan, and liveliness settings as parameters - qos_profile->deadline = rmw_qos_profile_default.deadline; - qos_profile->lifespan = rmw_qos_profile_default.lifespan; - qos_profile->liveliness = rmw_qos_profile_default.liveliness; - qos_profile->liveliness_lease_duration = rmw_qos_profile_default.liveliness_lease_duration; + RCLJAVA_QOS_SET_RMW_TIME(qos_profile, deadline, deadline_sec, deadline_nanos); + RCLJAVA_QOS_SET_RMW_TIME(qos_profile, lifespan, lifespan_sec, lifespan_nanos); + qos_profile->liveliness = static_cast(liveliness); + RCLJAVA_QOS_SET_RMW_TIME( + qos_profile, liveliness_lease_duration, liveliness_lease_sec, liveliness_lease_nanos); qos_profile->avoid_ros_namespace_conventions = avoidROSNamespaceConventions; return reinterpret_cast(qos_profile); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java b/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java index c4be74ef..15a703c8 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java +++ b/rcljava/src/main/java/org/ros2/rcljava/RCLJava.java @@ -301,18 +301,22 @@ public static synchronized void shutdown() { } public static long convertQoSProfileToHandle(final QoSProfile qosProfile) { - int history = qosProfile.getHistory().getValue(); - int depth = qosProfile.getDepth(); - int reliability = qosProfile.getReliability().getValue(); - int durability = qosProfile.getDurability().getValue(); - boolean avoidROSNamespaceConventions = qosProfile.getAvoidROSNamespaceConventions(); - return nativeConvertQoSProfileToHandle( - history, depth, reliability, durability, avoidROSNamespaceConventions); + qosProfile.getHistory().getValue(), + qosProfile.getDepth(), + qosProfile.getReliability().getValue(), + qosProfile.getDurability().getValue(), + qosProfile.getDeadline().getSeconds(), qosProfile.getDeadline().getNano(), + qosProfile.getLifespan().getSeconds(), qosProfile.getLifespan().getNano(), + qosProfile.getLiveliness().getValue(), qosProfile.getLivelinessLeaseDuration().getSeconds(), + qosProfile.getLivelinessLeaseDuration().getNano(), + qosProfile.getAvoidROSNamespaceConventions()); } - private static native long nativeConvertQoSProfileToHandle(int history, int depth, - int reliability, int durability, boolean avoidROSNamespaceConventions); + private static native long nativeConvertQoSProfileToHandle( + int history, int depth, int reliability, int durability, long deadlineSec, int deadlineNanos, + long lifespanSec, int lifespanNanos, int liveliness, long livelinessLeaseSec, + int livelinessLeaseNanos, boolean avoidROSNamespaceConventions); public static void disposeQoSProfile(final long qosProfileHandle) { nativeDisposeQoSProfile(qosProfileHandle); diff --git a/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java b/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java index 8b8a4c71..15b81712 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java +++ b/rcljava/src/main/java/org/ros2/rcljava/qos/QoSProfile.java @@ -15,21 +15,38 @@ package org.ros2.rcljava.qos; +import java.time.Duration; + import org.ros2.rcljava.qos.policies.Durability; import org.ros2.rcljava.qos.policies.History; +import org.ros2.rcljava.qos.policies.Liveliness; import org.ros2.rcljava.qos.policies.QoSPolicy; import org.ros2.rcljava.qos.policies.Reliability; public class QoSProfile { - private final History history; + // TODO(ivanpauno): Update all qos policies in a way that the objects are created from RCL, + // to avoid depending on the enum values. + private History history; + + private int depth; + + private Reliability reliability; + + private Durability durability; - private final int depth; + private Duration deadline = Duration.ofSeconds(0, 0); - private final Reliability reliability; + private Duration lifespan = Duration.ofSeconds(0, 0); - private final Durability durability; + private Liveliness liveliness = Liveliness.SYSTEM_DEFAULT; - private final boolean avoidROSNamespaceConventions; + private Duration livelinessLeaseDuration = Duration.ofSeconds(0, 0); + + private boolean avoidROSNamespaceConventions; + + public QoSProfile(int depth) { + this(History.KEEP_LAST, depth, Reliability.RELIABLE, Durability.VOLATILE, false); + } public QoSProfile(History history, int depth, Reliability reliability, Durability durability, boolean avoidROSNamespaceConventions) { @@ -44,39 +61,127 @@ public final History getHistory() { return this.history; } + public final QoSProfile setHistory(final History history) { + this.history = history; + return this; + } + public final int getDepth() { return this.depth; } + public final QoSProfile setDepth(final int depth) { + this.depth = depth; + return this; + } + public final Reliability getReliability() { return this.reliability; } + public final QoSProfile setReliability(final Reliability reliability) { + this.reliability = reliability; + return this; + } + public final Durability getDurability() { return this.durability; } + public final QoSProfile setDurability(final Durability durability) { + this.durability = durability; + return this; + } + + public final Duration getDeadline() { + return this.deadline; + } + + public final QoSProfile setDeadline(final Duration deadline) { + this.deadline = deadline; + return this; + } + + public final Duration getLifespan() { + return this.lifespan; + } + + public final QoSProfile setLifespan(final Duration lifespan) { + this.lifespan = lifespan; + return this; + } + + public final Liveliness getLiveliness() { + return this.liveliness; + } + + public final QoSProfile setLiveliness(final Liveliness liveliness) { + this.liveliness = liveliness; + return this; + } + + public final Duration getLivelinessLeaseDuration() { + return this.livelinessLeaseDuration; + } + + public final QoSProfile setLivelinessLeaseDuration(final Duration livelinessLeaseDuration) { + this.livelinessLeaseDuration = livelinessLeaseDuration; + return this; + } + public final boolean getAvoidROSNamespaceConventions() { return this.avoidROSNamespaceConventions; } + public final QoSProfile setAvoidROSNamespaceConventions(final boolean avoidROSConventions) { + this.avoidROSNamespaceConventions = avoidROSConventions; + return this; + } + + // TODO(ivanpauno): refactor all static default profiles methods, + // so that the return value is get from the rmw definition directly + // (leveraging a native function). + public static final QoSProfile defaultProfile() { + return new QoSProfile(10); + } + + public static final QoSProfile systemDefault() { + return new QoSProfile( + History.SYSTEM_DEFAULT, QoSProfile.DEPTH_SYSTEM_DEFAULT, + Reliability.SYSTEM_DEFAULT, Durability.SYSTEM_DEFAULT, false); + } + + public static final QoSProfile sensorData() { + return new QoSProfile( + History.KEEP_LAST, 5, Reliability.BEST_EFFORT, Durability.VOLATILE, false); + } + + public static final QoSProfile parametersDefault() { + return new QoSProfile( + History.KEEP_LAST, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + } + + public static final QoSProfile servicesDefault() { + return new QoSProfile( + History.KEEP_LAST, 10, Reliability.RELIABLE, Durability.VOLATILE, false); + } + + public static final QoSProfile parameterEventsDefault() { + return new QoSProfile( + History.KEEP_ALL, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + } + public static final int DEPTH_SYSTEM_DEFAULT = 0; - public static final QoSProfile SENSOR_DATA = - new QoSProfile(History.KEEP_LAST, 5, Reliability.BEST_EFFORT, Durability.VOLATILE, false); + public static final QoSProfile SENSOR_DATA = sensorData(); - public static final QoSProfile PARAMETERS = - new QoSProfile(History.KEEP_LAST, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile PARAMETERS = parametersDefault(); - public static final QoSProfile DEFAULT = - new QoSProfile(History.KEEP_LAST, 10, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile DEFAULT = defaultProfile(); - public static final QoSProfile SERVICES_DEFAULT = - new QoSProfile(History.KEEP_LAST, 10, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile SERVICES_DEFAULT = servicesDefault(); - public static final QoSProfile PARAMETER_EVENTS = - new QoSProfile(History.KEEP_ALL, 1000, Reliability.RELIABLE, Durability.VOLATILE, false); + public static final QoSProfile PARAMETER_EVENTS = parameterEventsDefault(); - public static final QoSProfile SYSTEM_DEFAULT = new QoSProfile(History.SYSTEM_DEFAULT, - DEPTH_SYSTEM_DEFAULT, Reliability.SYSTEM_DEFAULT, Durability.SYSTEM_DEFAULT, false); + public static final QoSProfile SYSTEM_DEFAULT = systemDefault(); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java b/rcljava/src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java new file mode 100644 index 00000000..0a814e7f --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/qos/policies/Liveliness.java @@ -0,0 +1,32 @@ +/* Copyright 2020 Open Source Robotics Foundation, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ros2.rcljava.qos.policies; + +public enum Liveliness implements QoSPolicy { + SYSTEM_DEFAULT(0), + AUTOMATIC(1), + MANUAL_BY_TOPIC(3); + + private final int value; + + Liveliness(final int value) { + this.value = value; + } + + public int getValue() { + return value; + } +} diff --git a/rcljava/src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java b/rcljava/src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java new file mode 100644 index 00000000..dbda21bf --- /dev/null +++ b/rcljava/src/test/java/org/ros2/rcljava/qos/QoSProfileTest.java @@ -0,0 +1,73 @@ +/* Copyright 2020 Open Source Robotics Foundation, Inc. + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package org.ros2.rcljava.qos; + +import static org.junit.Assert.assertEquals; + +import java.lang.reflect.Method; + +import org.junit.BeforeClass; +import org.junit.Test; + +import org.ros2.rcljava.qos.QoSProfile; +import org.ros2.rcljava.qos.policies.Durability; +import org.ros2.rcljava.qos.policies.History; +import org.ros2.rcljava.qos.policies.Reliability; + +public class QoSProfileTest { + @BeforeClass + public static void setupOnce() throws Exception { + // Just to quiet down warnings + try + { + // Configure log4j. Doing this dynamically so that Android does not complain about missing + // the log4j JARs, SLF4J uses Android's native logging mechanism instead. + Class c = Class.forName("org.apache.log4j.BasicConfigurator"); + Method m = c.getDeclaredMethod("configure", (Class[]) null); + Object o = m.invoke(null, (Object[]) null); + } + catch (Exception e) + { + e.printStackTrace(); + } + } + + @Test + public final void testQoSProfileBasic() { + QoSProfile qosProfile = new QoSProfile( + History.KEEP_LAST, 1, Reliability.RELIABLE, Durability.VOLATILE, false); + assertEquals(qosProfile.getHistory(), History.KEEP_LAST); + assertEquals(qosProfile.getDepth(), 1); + assertEquals(qosProfile.getReliability(), Reliability.RELIABLE); + assertEquals(qosProfile.getDurability(), Durability.VOLATILE); + assertEquals(qosProfile.getAvoidROSNamespaceConventions(), false); + } + + @Test + public final void testQoSProfileChaining() { + QoSProfile qosProfile = QoSProfile.defaultProfile() + .setHistory(History.KEEP_ALL) + .setDepth(11) + .setReliability(Reliability.BEST_EFFORT) + .setDurability(Durability.TRANSIENT_LOCAL) + .setAvoidROSNamespaceConventions(true); + assertEquals(qosProfile.getHistory(), History.KEEP_ALL); + assertEquals(qosProfile.getDepth(), 11); + assertEquals(qosProfile.getReliability(), Reliability.BEST_EFFORT); + assertEquals(qosProfile.getDurability(), Durability.TRANSIENT_LOCAL); + assertEquals(qosProfile.getAvoidROSNamespaceConventions(), true); + } +} From c3cf76f4adef779e304b05f600bcca4825a15e6c Mon Sep 17 00:00:00 2001 From: Ivan Santiago Paunovic Date: Wed, 12 Aug 2020 10:10:59 -0300 Subject: [PATCH 2/3] Add abstractions and minimal implementation of rcl events (osrf/ros2_java#4) Signed-off-by: Ivan Santiago Paunovic --- rcljava/CMakeLists.txt | 4 + ...org_ros2_rcljava_events_EventHandlerImpl.h | 44 +++++ ...g_ros2_rcljava_events_EventHandlerImpl.cpp | 71 ++++++++ .../org/ros2/rcljava/events/EventHandler.java | 51 ++++++ .../ros2/rcljava/events/EventHandlerImpl.java | 165 ++++++++++++++++++ .../org/ros2/rcljava/events/EventStatus.java | 43 +++++ .../rcljava/events/PublisherEventStatus.java | 28 +++ .../events/SubscriptionEventStatus.java | 28 +++ 8 files changed, 434 insertions(+) create mode 100644 rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h create mode 100644 rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp create mode 100644 rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java create mode 100644 rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java create mode 100644 rcljava/src/main/java/org/ros2/rcljava/events/EventStatus.java create mode 100644 rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java create mode 100644 rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java diff --git a/rcljava/CMakeLists.txt b/rcljava/CMakeLists.txt index 366c7f77..5bb18931 100644 --- a/rcljava/CMakeLists.txt +++ b/rcljava/CMakeLists.txt @@ -59,6 +59,7 @@ set(${PROJECT_NAME}_jni_sources "src/main/cpp/org_ros2_rcljava_client_ClientImpl.cpp" "src/main/cpp/org_ros2_rcljava_contexts_ContextImpl.cpp" "src/main/cpp/org_ros2_rcljava_executors_BaseExecutor.cpp" + "src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp" "src/main/cpp/org_ros2_rcljava_node_NodeImpl.cpp" "src/main/cpp/org_ros2_rcljava_publisher_PublisherImpl.cpp" "src/main/cpp/org_ros2_rcljava_service_ServiceImpl.cpp" @@ -125,6 +126,9 @@ set(${PROJECT_NAME}_sources "src/main/java/org/ros2/rcljava/consumers/BiConsumer.java" "src/main/java/org/ros2/rcljava/consumers/Consumer.java" "src/main/java/org/ros2/rcljava/consumers/TriConsumer.java" + "src/main/java/org/ros2/rcljava/events/EventHandler.java" + "src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java" + "src/main/java/org/ros2/rcljava/events/EventStatus.java" "src/main/java/org/ros2/rcljava/executors/AnyExecutable.java" "src/main/java/org/ros2/rcljava/executors/BaseExecutor.java" "src/main/java/org/ros2/rcljava/executors/Executor.java" diff --git a/rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h b/rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h new file mode 100644 index 00000000..2416ca08 --- /dev/null +++ b/rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h @@ -0,0 +1,44 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +/* Header for class org_ros2_rcljava_events_EventHandlerImpl */ + +#ifndef ORG_ROS2_RCLJAVA_EVENTS_EVENTHANDLERIMPL_H_ +#define ORG_ROS2_RCLJAVA_EVENTS_EVENTHANDLERIMPL_H_ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Class: org_ros2_rcljava_events_EventHandlerImpl + * Method: nativeDispose + * Signature: (J)V + */ +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_events_EventHandlerImpl_nativeDispose(JNIEnv *, jclass, jlong event_handle); + +/* + * Class: org_ros2_rcljava_events_EventHandlerImpl + * Method: nativeTake + * Signature: (JJ)V + */ +JNIEXPORT void +JNICALL Java_org_ros2_rcljava_events_EventHandlerImpl_nativeTake( + JNIEnv *, jclass, jlong event_handle, jlong event_status_handle); + +#ifdef __cplusplus +} +#endif +#endif // ORG_ROS2_RCLJAVA_EVENTS_EVENTHANDLERIMPL_H_ diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp new file mode 100644 index 00000000..c23fd533 --- /dev/null +++ b/rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp @@ -0,0 +1,71 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include + +#include + +#include "rcl/error_handling.h" +#include "rcl/event.h" + +#include "rcljava_common/exceptions.hpp" + +#include "org_ros2_rcljava_events_EventHandlerImpl.h" + +using rcljava_common::exceptions::rcljava_throw_exception; +using rcljava_common::exceptions::rcljava_throw_rclexception; + +JNIEXPORT void JNICALL +Java_org_ros2_rcljava_events_EventHandlerImpl_nativeDispose( + JNIEnv * env, jclass, jlong event_handle) +{ + if (event_handle == 0) { + // everything is ok, already destroyed + return; + } + + auto * event = reinterpret_cast(event_handle); + + rcl_ret_t ret = rcl_event_fini(event); + + if (RCL_RET_OK != ret) { + std::string msg = "Failed to destroy event: " + std::string(rcl_get_error_string().str); + rcl_reset_error(); + rcljava_throw_exception(env, "java/lang/IllegalStateException", msg); + } +} + +JNIEXPORT void JNICALL +Java_org_ros2_rcljava_events_EventHandlerImpl_nativeTake(JNIEnv *env, jclass, jlong event_handle, jlong event_status_handle) +{ + auto * event = reinterpret_cast(event_handle); + if (!event) { + rcljava_throw_exception( + env, + "java/lang/IllegalArgumentException", + "The underlying rcl_event_t has been already disposed"); + } + void * event_status = reinterpret_cast(event_status_handle); + if (!event_status) { + rcljava_throw_exception( + env, + "java/lang/IllegalArgumentException", + "The passed event status is NULL"); + } + rcl_ret_t ret = rcl_take_event(event, event_status); + if (RCL_RET_OK != ret) { + rcljava_throw_rclexception(env, ret, rcl_get_error_string().str); + rcl_reset_error(); + } +} diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java new file mode 100644 index 00000000..7df0f4fb --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java @@ -0,0 +1,51 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.ros2.rcljava.events; + +import java.lang.ref.WeakReference; + +import org.ros2.rcljava.interfaces.Disposable; +import org.ros2.rcljava.events.EventStatus; + +/** + * This class serves as a bridge between a rcl_event_t and RCLJava. + * An EventHandler must be created via + * @{link Publisher#createEventHandler(Class<T>, Consumer<T>)} + * @{link Subscription#createEventHandler(Class<T>, Consumer<T>)} + * + * @param The event status type. + * @param The parent class type. + */ +public interface EventHandler extends Disposable { + /** + * @return The event status type. + */ + Class getEventStatusType(); + + /** + * @return The parent entity type. + */ + Class getParentType(); + + /** + * @return A weak reference to the parent. + */ + WeakReference getParentReference(); + + /** + * @return Execute the registered event callback + */ + void executeCallback(); +} diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java new file mode 100644 index 00000000..013ec29a --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java @@ -0,0 +1,165 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.ros2.rcljava.events; + +import java.lang.ref.WeakReference; +import java.lang.reflect.InvocationTargetException; + +import org.ros2.rcljava.common.JNIUtils; +import org.ros2.rcljava.consumers.Consumer; +import org.ros2.rcljava.events.EventHandler; +import org.ros2.rcljava.events.EventStatus; +import org.ros2.rcljava.interfaces.Disposable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class serves as a bridge between a rcl_event_t and RCLJava. + * An EventHandler must be created via + * @{link Publisher#createEventHandler(Class<T>, Consumer<T>)} + * @{link Subscription#createEventHandler(Class<T>, Consumer<T>)} + * + * @param The status event type. + * @param The parent class type. + */ +public class EventHandlerImpl< + T extends EventStatus, + ParentT extends Disposable> +implements EventHandler { + private static final Logger logger = LoggerFactory.getLogger(EventHandlerImpl.class); + + static { + try { + JNIUtils.loadImplementation(EventHandlerImpl.class); + } catch (UnsatisfiedLinkError ule) { + logger.error("Native code library failed to load.\n" + ule); + System.exit(1); + } + } + + /** + * Constructor. + * + * @param parentType The Class type of the parent. + * It can be either a @{link org.ros2.rcljava.Publisher} or a + * @{link org.ros2.rcljava.Subscription} class. + * @param parentReference A {@link java.lang.ref.WeakReference} to the + * @{link org.ros2.rcljava.Publisher} or @{link org.ros2.rcljava.Subscription} + * that created this event handler. + * @param handle A pointer to the underlying ROS 2 event structure, as an integer. + * Must not be zero. + * @param eventStatusType The Class of the messages that this + * subscription will receive. We need this because of Java's type erasure, + * which doesn't allow us to use the generic parameter of + * @{link org.ros2.rcljava.Subscription} directly. + * @param callback The callback function that will be called when the event + * is triggered. + */ + public EventHandlerImpl( + final Class parentType, + final WeakReference parentReference, + final long handle, + final Class eventStatusType, + final Consumer callback) { + this.parentType = parentType; + this.parentReference = parentReference; + this.handle = handle; + this.eventStatusType = eventStatusType; + this.callback = callback; + } + + /** + * {@inheritDoc} + */ + public final Class getEventStatusType() { + return this.eventStatusType; + } + + /** + * {@inheritDoc} + */ + public final Class getParentType() { + return this.parentType; + } + + /** + * {@inheritDoc} + */ + public final WeakReference getParentReference() { + return this.parentReference; + } + + /** + * {@inheritDoc} + */ + public final long getHandle() { + return this.handle; + } + + /** + * Destroy a ROS 2 event (rcl_event_t). + * + * @param handle A pointer to the underlying ROS 2 event structure, + * as an integer. Must not be zero. + */ + private static native void nativeDispose(long handle); + + /** + * {@inheritDoc} + */ + public final void dispose() { + nativeDispose(this.handle); + this.handle = 0; + } + + /** + * Takes the RCL event status and returns a pointer to it. + * + * @param event_handle A pointer to the underlying ROS 2 event (rcl_event_t). + * @param event_status_handle A pointer to the underlying ROS 2 event status (void *). + * Must not be zero. + */ + private static native void nativeTake(long event_handle, long event_status_handle); + + /** + * {@inheritDoc} + */ + public final void executeCallback() { + T eventStatus = null; + try { + eventStatus = this.eventStatusType.getDeclaredConstructor().newInstance(); + } catch (NoSuchMethodException nme) { + nme.printStackTrace(); + } catch (InvocationTargetException ite) { + ite.printStackTrace(); + } catch (InstantiationException ie) { + ie.printStackTrace(); + } catch (IllegalAccessException iae) { + iae.printStackTrace(); + } + long nativeEventStatusHandle = eventStatus.allocateRCLStatusEvent(); + nativeTake(this.handle, nativeEventStatusHandle); + eventStatus.fromRCLEvent(nativeEventStatusHandle); + eventStatus.deallocateRCLStatusEvent(nativeEventStatusHandle); + callback.accept(eventStatus); + } + + private final Class eventStatusType; + private final Class parentType; + private final WeakReference parentReference; + private long handle; + private final Consumer callback; +} diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/EventStatus.java b/rcljava/src/main/java/org/ros2/rcljava/events/EventStatus.java new file mode 100644 index 00000000..4b129826 --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/events/EventStatus.java @@ -0,0 +1,43 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.ros2.rcljava.events; + +import org.ros2.rcljava.interfaces.Disposable; + +/** + * This class serves as a bridge between ROS2's rcl event status types and RCLJava. + */ +public interface EventStatus { + /** + * Allocates an rcl event status. + * + * @return A pointer to the allocated status. + */ + long allocateRCLStatusEvent(); + + /** + * Deallocates an rcl event status. + * + * @param handle Pointer to a previously allocated event status. + */ + void deallocateRCLStatusEvent(long handle); + + /** + * Loads the event with the data of the rcl event status. + * + * @param handle A pointer to the underlying event status. + */ + void fromRCLEvent(long handle); +} diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java b/rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java new file mode 100644 index 00000000..b6c9881b --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java @@ -0,0 +1,28 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.ros2.rcljava.events; + +import org.ros2.rcljava.events.EventStatus; + +/** + * This class serves as a bridge between ROS2's rcl publisher event + * status types and RCLJava. + */ +public interface PublisherEventStatus extends EventStatus { + /** + * @return The status event type. + */ + int get_publisher_event_type(); +} diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java b/rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java new file mode 100644 index 00000000..224e9e53 --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java @@ -0,0 +1,28 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.ros2.rcljava.events; + +import org.ros2.rcljava.events.EventStatus; + +/** + * This class serves as a bridge between ROS2's rcl subscription event + * status types and RCLJava. + */ +public interface SubscriptionEventStatus extends EventStatus { + /** + * @return The status event type. + */ + int get_subscription_event_type(); +} From ac52a7cf6da426453d45d29b116c703c6d7049fb Mon Sep 17 00:00:00 2001 From: Ivan Santiago Paunovic Date: Tue, 18 Aug 2020 15:35:24 -0300 Subject: [PATCH 3/3] Add publisher liveliness lost event (osrf/ros2_java#9) * Create LivelinessLostEvent Signed-off-by: Ivan Santiago Paunovic * Fix linking issue Signed-off-by: Ivan Santiago Paunovic * Rename disposeEventHandler to removeEventHandler Signed-off-by: Ivan Santiago Paunovic * Use synchronized methods in EventHandlerImpl Signed-off-by: Ivan Santiago Paunovic * Use createEventHandler instead of registerEventHanlder Signed-off-by: Ivan Santiago Paunovic * nit Signed-off-by: Ivan Santiago Paunovic * Clarify ownership of the event handle Signed-off-by: Ivan Santiago Paunovic --- rcljava/CMakeLists.txt | 4 + ...org_ros2_rcljava_events_EventHandlerImpl.h | 3 +- ...events_publisher_statuses_LivelinessLost.h | 63 ++++++++++++++++ ...org_ros2_rcljava_publisher_PublisherImpl.h | 9 +++ ...g_ros2_rcljava_events_EventHandlerImpl.cpp | 3 +- ...ents_publisher_statuses_LivelinessLost.cpp | 73 +++++++++++++++++++ ...g_ros2_rcljava_publisher_PublisherImpl.cpp | 30 ++++++++ .../org/ros2/rcljava/events/EventHandler.java | 10 --- .../ros2/rcljava/events/EventHandlerImpl.java | 55 ++++---------- .../rcljava/events/PublisherEventStatus.java | 2 +- .../events/SubscriptionEventStatus.java | 2 +- .../publisher_statuses/LivelinessLost.java | 65 +++++++++++++++++ .../org/ros2/rcljava/publisher/Publisher.java | 23 ++++++ .../ros2/rcljava/publisher/PublisherImpl.java | 53 ++++++++++++++ .../ros2/rcljava/publisher/PublisherTest.java | 19 +++++ 15 files changed, 358 insertions(+), 56 deletions(-) create mode 100644 rcljava/include/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.h create mode 100644 rcljava/src/main/cpp/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.cpp create mode 100644 rcljava/src/main/java/org/ros2/rcljava/events/publisher_statuses/LivelinessLost.java diff --git a/rcljava/CMakeLists.txt b/rcljava/CMakeLists.txt index 5bb18931..159d45e8 100644 --- a/rcljava/CMakeLists.txt +++ b/rcljava/CMakeLists.txt @@ -60,6 +60,7 @@ set(${PROJECT_NAME}_jni_sources "src/main/cpp/org_ros2_rcljava_contexts_ContextImpl.cpp" "src/main/cpp/org_ros2_rcljava_executors_BaseExecutor.cpp" "src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp" + "src/main/cpp/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.cpp" "src/main/cpp/org_ros2_rcljava_node_NodeImpl.cpp" "src/main/cpp/org_ros2_rcljava_publisher_PublisherImpl.cpp" "src/main/cpp/org_ros2_rcljava_service_ServiceImpl.cpp" @@ -129,6 +130,9 @@ set(${PROJECT_NAME}_sources "src/main/java/org/ros2/rcljava/events/EventHandler.java" "src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java" "src/main/java/org/ros2/rcljava/events/EventStatus.java" + "src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java" + "src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java" + "src/main/java/org/ros2/rcljava/events/publisher_statuses/LivelinessLost.java" "src/main/java/org/ros2/rcljava/executors/AnyExecutable.java" "src/main/java/org/ros2/rcljava/executors/BaseExecutor.java" "src/main/java/org/ros2/rcljava/executors/Executor.java" diff --git a/rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h b/rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h index 2416ca08..8dbf6ce2 100644 --- a/rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h +++ b/rcljava/include/org_ros2_rcljava_events_EventHandlerImpl.h @@ -27,7 +27,8 @@ extern "C" { * Signature: (J)V */ JNIEXPORT void -JNICALL Java_org_ros2_rcljava_events_EventHandlerImpl_nativeDispose(JNIEnv *, jclass, jlong event_handle); +JNICALL Java_org_ros2_rcljava_events_EventHandlerImpl_nativeDispose( + JNIEnv *, jclass, jlong event_handle); /* * Class: org_ros2_rcljava_events_EventHandlerImpl diff --git a/rcljava/include/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.h b/rcljava/include/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.h new file mode 100644 index 00000000..53126f7b --- /dev/null +++ b/rcljava/include/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.h @@ -0,0 +1,63 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include +/* Header for class org_ros2_rcljava_events_publisher_statuses_LivelinessLost */ + +#ifndef ORG_ROS2_RCLJAVA_EVENTS_PUBLISHER_STATUSES_LIVELINESSLOST_H_ +#define ORG_ROS2_RCLJAVA_EVENTS_PUBLISHER_STATUSES_LIVELINESSLOST_H_ +#ifdef __cplusplus +extern "C" { +#endif + +/* + * Class: org_ros2_rcljava_events_publisher_statuses_LivelinessLost + * Method: nativeAllocateRCLStatusEvent + * Signature: ()J + */ +JNIEXPORT jlong JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeAllocateRCLStatusEvent( + JNIEnv *, jclass); + +/* + * Class: org_ros2_rcljava_events_publisher_statuses_LivelinessLost + * Method: nativeDeallocateRCLStatusEvent + * Signature: (J)V + */ +JNIEXPORT void JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeDeallocateRCLStatusEvent( + JNIEnv *, jclass, jlong); + +/* + * Class: org_ros2_rcljava_events_publisher_statuses_LivelinessLost + * Method: nativeFromRCLEvent + * Signature: (J)V + */ +JNIEXPORT void JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeFromRCLEvent( + JNIEnv *, jobject, jlong); + +/* + * Class: org_ros2_rcljava_events_publisher_statuses_LivelinessLost + * Method: nativeGetPublisherEventType + * Signature: ()I + */ +JNIEXPORT jint JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeGetPublisherEventType( + JNIEnv *, jclass); + +#ifdef __cplusplus +} +#endif +#endif // ORG_ROS2_RCLJAVA_EVENTS_PUBLISHER_STATUSES_LIVELINESSLOST_H_ diff --git a/rcljava/include/org_ros2_rcljava_publisher_PublisherImpl.h b/rcljava/include/org_ros2_rcljava_publisher_PublisherImpl.h index befe7447..9236fdda 100644 --- a/rcljava/include/org_ros2_rcljava_publisher_PublisherImpl.h +++ b/rcljava/include/org_ros2_rcljava_publisher_PublisherImpl.h @@ -38,6 +38,15 @@ JNIEXPORT void JNICALL Java_org_ros2_rcljava_publisher_PublisherImpl_nativeDispose( JNIEnv *, jclass, jlong, jlong); +/* + * Class: org_ros2_rcljava_publisher_PublisherImpl + * Method: nativeCreateEvent + * Signature: (JJ)J + */ +JNIEXPORT jlong +JNICALL Java_org_ros2_rcljava_publisher_PublisherImpl_nativeCreateEvent( + JNIEnv *, jclass, jlong, jint); + #ifdef __cplusplus } #endif diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp index c23fd533..89e2faba 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_events_EventHandlerImpl.cpp @@ -47,7 +47,8 @@ Java_org_ros2_rcljava_events_EventHandlerImpl_nativeDispose( } JNIEXPORT void JNICALL -Java_org_ros2_rcljava_events_EventHandlerImpl_nativeTake(JNIEnv *env, jclass, jlong event_handle, jlong event_status_handle) +Java_org_ros2_rcljava_events_EventHandlerImpl_nativeTake( + JNIEnv * env, jclass, jlong event_handle, jlong event_status_handle) { auto * event = reinterpret_cast(event_handle); if (!event) { diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.cpp new file mode 100644 index 00000000..95bad689 --- /dev/null +++ b/rcljava/src/main/cpp/org_ros2_rcljava_events_publisher_statuses_LivelinessLost.cpp @@ -0,0 +1,73 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +#include "org_ros2_rcljava_events_publisher_statuses_LivelinessLost.h" + +#include +#include + +#include "rmw/events_statuses/liveliness_lost.h" +#include "rcl/event.h" +#include "rcljava_common/exceptions.hpp" + +using rcljava_common::exceptions::rcljava_throw_exception; + +JNIEXPORT jlong JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeAllocateRCLStatusEvent( + JNIEnv * env, jclass) +{ + void * p = malloc(sizeof(rmw_liveliness_lost_status_t)); + if (!p) { + rcljava_throw_exception( + env, "java/lang/OutOfMemoryError", "failed to allocate liveliness lost status"); + } + return reinterpret_cast(p); +} + +JNIEXPORT void JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeDeallocateRCLStatusEvent( + JNIEnv *, jclass, jlong handle) +{ + free(reinterpret_cast(handle)); +} + +JNIEXPORT void JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeFromRCLEvent( + JNIEnv * env, jobject self, jlong handle) +{ + auto * p = reinterpret_cast(handle); + if (!p) { + rcljava_throw_exception( + env, "java/lang/IllegalArgumentException", "passed rmw object handle is NULL"); + } + // TODO(ivanpauno): class and field lookup could be done at startup time + jclass clazz = env->GetObjectClass(self); + jfieldID total_count_fid = env->GetFieldID(clazz, "total_count", "I"); + if (env->ExceptionCheck()) { + return; + } + jfieldID total_count_change_fid = env->GetFieldID(clazz, "total_count_change", "I"); + if (env->ExceptionCheck()) { + return; + } + env->SetIntField(self, total_count_fid, p->total_count); + env->SetIntField(self, total_count_change_fid, p->total_count_change); +} + +JNIEXPORT jint JNICALL +Java_org_ros2_rcljava_events_publisher_1statuses_LivelinessLost_nativeGetPublisherEventType( + JNIEnv *, jclass) +{ + return RCL_PUBLISHER_LIVELINESS_LOST; +} diff --git a/rcljava/src/main/cpp/org_ros2_rcljava_publisher_PublisherImpl.cpp b/rcljava/src/main/cpp/org_ros2_rcljava_publisher_PublisherImpl.cpp index 6cef82c2..ea5d9feb 100644 --- a/rcljava/src/main/cpp/org_ros2_rcljava_publisher_PublisherImpl.cpp +++ b/rcljava/src/main/cpp/org_ros2_rcljava_publisher_PublisherImpl.cpp @@ -20,6 +20,7 @@ #include #include "rcl/error_handling.h" +#include "rcl/event.h" #include "rcl/node.h" #include "rcl/rcl.h" #include "rmw/rmw.h" @@ -29,6 +30,7 @@ #include "org_ros2_rcljava_publisher_PublisherImpl.h" +using rcljava_common::exceptions::rcljava_throw_exception; using rcljava_common::exceptions::rcljava_throw_rclexception; using rcljava_common::signatures::convert_from_java_signature; using rcljava_common::signatures::destroy_ros_message_signature; @@ -90,3 +92,31 @@ Java_org_ros2_rcljava_publisher_PublisherImpl_nativeDispose( rcljava_throw_rclexception(env, ret, msg); } } + +JNIEXPORT jlong JNICALL +Java_org_ros2_rcljava_publisher_PublisherImpl_nativeCreateEvent( + JNIEnv * env, jclass, jlong publisher_handle, jint event_type) +{ + auto * publisher = reinterpret_cast(publisher_handle); + if (!publisher) { + rcljava_throw_exception( + env, "java/lang/IllegalArgumentException", "passed rcl_publisher_t handle is NULL"); + return 0; + } + auto * event = static_cast(malloc(sizeof(rcl_event_t))); + if (!event) { + rcljava_throw_exception(env, "java/lang/OutOfMemoryError", "failed to allocate rcl_event_t"); + return 0; + } + *event = rcl_get_zero_initialized_event(); + rcl_ret_t ret = rcl_publisher_event_init( + event, publisher, static_cast(event_type)); + if (RCL_RET_OK != ret) { + std::string msg = "Failed to create event: " + std::string(rcl_get_error_string().str); + rcl_reset_error(); + rcljava_throw_rclexception(env, ret, msg); + free(event); + return 0; + } + return reinterpret_cast(event); +} diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java index 7df0f4fb..6f6c7985 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java +++ b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandler.java @@ -29,16 +29,6 @@ * @param The parent class type. */ public interface EventHandler extends Disposable { - /** - * @return The event status type. - */ - Class getEventStatusType(); - - /** - * @return The parent entity type. - */ - Class getParentType(); - /** * @return A weak reference to the parent. */ diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java index 013ec29a..25545c7f 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java +++ b/rcljava/src/main/java/org/ros2/rcljava/events/EventHandlerImpl.java @@ -16,6 +16,7 @@ import java.lang.ref.WeakReference; import java.lang.reflect.InvocationTargetException; +import java.util.function.Supplier; import org.ros2.rcljava.common.JNIUtils; import org.ros2.rcljava.consumers.Consumer; @@ -53,48 +54,28 @@ public class EventHandlerImpl< /** * Constructor. * - * @param parentType The Class type of the parent. - * It can be either a @{link org.ros2.rcljava.Publisher} or a - * @{link org.ros2.rcljava.Subscription} class. * @param parentReference A {@link java.lang.ref.WeakReference} to the * @{link org.ros2.rcljava.Publisher} or @{link org.ros2.rcljava.Subscription} * that created this event handler. * @param handle A pointer to the underlying ROS 2 event structure, as an integer. * Must not be zero. - * @param eventStatusType The Class of the messages that this - * subscription will receive. We need this because of Java's type erasure, - * which doesn't allow us to use the generic parameter of - * @{link org.ros2.rcljava.Subscription} directly. + * Ownership of the passed `handle` is taken, and this object is responsible + * of disposing it. That can be done using @{link EventHandler#dispose()}. + * @param eventStatusFactory Factory of an event status. * @param callback The callback function that will be called when the event * is triggered. */ public EventHandlerImpl( - final Class parentType, final WeakReference parentReference, final long handle, - final Class eventStatusType, + final Supplier eventStatusFactory, final Consumer callback) { - this.parentType = parentType; this.parentReference = parentReference; this.handle = handle; - this.eventStatusType = eventStatusType; + this.eventStatusFactory = eventStatusFactory; this.callback = callback; } - /** - * {@inheritDoc} - */ - public final Class getEventStatusType() { - return this.eventStatusType; - } - - /** - * {@inheritDoc} - */ - public final Class getParentType() { - return this.parentType; - } - /** * {@inheritDoc} */ @@ -120,8 +101,10 @@ public final long getHandle() { /** * {@inheritDoc} */ - public final void dispose() { - nativeDispose(this.handle); + public synchronized final void dispose() { + if (this.handle != 0) { + nativeDispose(this.handle); + } this.handle = 0; } @@ -137,19 +120,8 @@ public final void dispose() { /** * {@inheritDoc} */ - public final void executeCallback() { - T eventStatus = null; - try { - eventStatus = this.eventStatusType.getDeclaredConstructor().newInstance(); - } catch (NoSuchMethodException nme) { - nme.printStackTrace(); - } catch (InvocationTargetException ite) { - ite.printStackTrace(); - } catch (InstantiationException ie) { - ie.printStackTrace(); - } catch (IllegalAccessException iae) { - iae.printStackTrace(); - } + public synchronized final void executeCallback() { + T eventStatus = eventStatusFactory.get(); long nativeEventStatusHandle = eventStatus.allocateRCLStatusEvent(); nativeTake(this.handle, nativeEventStatusHandle); eventStatus.fromRCLEvent(nativeEventStatusHandle); @@ -157,8 +129,7 @@ public final void executeCallback() { callback.accept(eventStatus); } - private final Class eventStatusType; - private final Class parentType; + private final Supplier eventStatusFactory; private final WeakReference parentReference; private long handle; private final Consumer callback; diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java b/rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java index b6c9881b..a1eb3375 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java +++ b/rcljava/src/main/java/org/ros2/rcljava/events/PublisherEventStatus.java @@ -24,5 +24,5 @@ public interface PublisherEventStatus extends EventStatus { /** * @return The status event type. */ - int get_publisher_event_type(); + int getPublisherEventType(); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java b/rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java index 224e9e53..55d93372 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java +++ b/rcljava/src/main/java/org/ros2/rcljava/events/SubscriptionEventStatus.java @@ -24,5 +24,5 @@ public interface SubscriptionEventStatus extends EventStatus { /** * @return The status event type. */ - int get_subscription_event_type(); + int getSubscriptionEventType(); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/events/publisher_statuses/LivelinessLost.java b/rcljava/src/main/java/org/ros2/rcljava/events/publisher_statuses/LivelinessLost.java new file mode 100644 index 00000000..615126be --- /dev/null +++ b/rcljava/src/main/java/org/ros2/rcljava/events/publisher_statuses/LivelinessLost.java @@ -0,0 +1,65 @@ +// Copyright 2020 Open Source Robotics Foundation, Inc. +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package org.ros2.rcljava.events.publisher_statuses; + +import java.util.function.Supplier; + +import org.ros2.rcljava.common.JNIUtils; +import org.ros2.rcljava.events.PublisherEventStatus; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * This class serves as a bridge between a rmw_liveliness_lost_status_t and RCLJava. + */ +public class LivelinessLost implements PublisherEventStatus { + int total_count; + int total_count_change; + + public final long allocateRCLStatusEvent() { + return nativeAllocateRCLStatusEvent(); + } + public final void deallocateRCLStatusEvent(long handle) { + nativeDeallocateRCLStatusEvent(handle); + } + public final void fromRCLEvent(long handle) { + nativeFromRCLEvent(handle); + } + public final int getPublisherEventType() { + return nativeGetPublisherEventType(); + } + // TODO(ivanpauno): Remove this when -source 8 can be used (method references for the win) + public static final Supplier factory = new Supplier() { + public LivelinessLost get() { + return new LivelinessLost(); + } + }; + + private static final Logger logger = LoggerFactory.getLogger(LivelinessLost.class); + static { + try { + JNIUtils.loadImplementation(LivelinessLost.class); + } catch (UnsatisfiedLinkError ule) { + logger.error("Native code library failed to load.\n" + ule); + System.exit(1); + } + } + + private static native long nativeAllocateRCLStatusEvent(); + private static native void nativeDeallocateRCLStatusEvent(long handle); + private native void nativeFromRCLEvent(long handle); + private static native int nativeGetPublisherEventType(); +} diff --git a/rcljava/src/main/java/org/ros2/rcljava/publisher/Publisher.java b/rcljava/src/main/java/org/ros2/rcljava/publisher/Publisher.java index 0d2427b7..d21eece2 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/publisher/Publisher.java +++ b/rcljava/src/main/java/org/ros2/rcljava/publisher/Publisher.java @@ -16,9 +16,13 @@ package org.ros2.rcljava.publisher; import java.lang.ref.WeakReference; +import java.util.function.Supplier; +import org.ros2.rcljava.consumers.Consumer; import org.ros2.rcljava.interfaces.Disposable; import org.ros2.rcljava.interfaces.MessageDefinition; +import org.ros2.rcljava.events.EventHandler; +import org.ros2.rcljava.events.PublisherEventStatus; import org.ros2.rcljava.node.Node; /** @@ -41,4 +45,23 @@ public interface Publisher extends Disposable { * that created this publisher. */ WeakReference getNodeReference(); + + /** + * Create an event handler. + * + * @param A publisher event status type. + * @param factory A factory that can instantiate an event status of type T. + * @param callback Callback that will be called when the event is triggered. + */ + EventHandler createEventHandler( + Supplier factory, Consumer callback); + + /** + * Remove a previously registered event handler. + * + * @param A publisher event status type. + * @param eventHandler An event handler that was registered previously in this object. + */ + void removeEventHandler( + EventHandler eventHandler); } diff --git a/rcljava/src/main/java/org/ros2/rcljava/publisher/PublisherImpl.java b/rcljava/src/main/java/org/ros2/rcljava/publisher/PublisherImpl.java index f3fa717b..2bba3892 100644 --- a/rcljava/src/main/java/org/ros2/rcljava/publisher/PublisherImpl.java +++ b/rcljava/src/main/java/org/ros2/rcljava/publisher/PublisherImpl.java @@ -16,10 +16,17 @@ package org.ros2.rcljava.publisher; import java.lang.ref.WeakReference; +import java.util.Collection; +import java.util.concurrent.LinkedBlockingQueue; +import java.util.function.Supplier; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import org.ros2.rcljava.consumers.Consumer; +import org.ros2.rcljava.events.EventHandler; +import org.ros2.rcljava.events.EventHandlerImpl; +import org.ros2.rcljava.events.PublisherEventStatus; import org.ros2.rcljava.RCLJava; import org.ros2.rcljava.common.JNIUtils; import org.ros2.rcljava.interfaces.MessageDefinition; @@ -53,6 +60,8 @@ public class PublisherImpl implements Publisher */ private final String topic; + private final Collection eventHandlers; + /** * Constructor. * @@ -67,6 +76,7 @@ public PublisherImpl( this.nodeReference = nodeReference; this.handle = handle; this.topic = topic; + this.eventHandlers = new LinkedBlockingQueue(); } /** @@ -101,6 +111,45 @@ public final WeakReference getNodeReference() { return this.nodeReference; } + /** + * {@inheritDoc} + */ + public final + EventHandler + createEventHandler(Supplier factory, Consumer callback) { + T status = factory.get(); + long eventHandle = nativeCreateEvent(this.handle, status.getPublisherEventType()); + EventHandler eventHandler = new EventHandlerImpl( + new WeakReference(this), eventHandle, factory, callback); + this.eventHandlers.add(eventHandler); + return eventHandler; + } + + /** + * {@inheritDoc} + */ + public final + void removeEventHandler( + EventHandler eventHandler) + { + if (!this.eventHandlers.remove(eventHandler)) { + throw new IllegalArgumentException("The passed eventHandler wasn't created by this publisher"); + } + eventHandler.dispose(); + } + + /** + * Create a publisher event (rcl_event_t). + * + * The ownership of the created event handle will immediately be transferred to an + * @{link EventHandlerImpl}, that will be responsible of disposing it. + * + * @param handle A pointer to the underlying ROS2 publisher structure. + * Must not be zero. + * @param eventType The rcl event type. + */ + private static native long nativeCreateEvent(long handle, int eventType); + /** * Destroy a ROS2 publisher (rcl_publisher_t). * @@ -115,6 +164,10 @@ public final WeakReference getNodeReference() { * {@inheritDoc} */ public final void dispose() { + for (EventHandler eventHandler : this.eventHandlers) { + eventHandler.dispose(); + } + this.eventHandlers.clear(); Node node = this.nodeReference.get(); if (node != null) { node.removePublisher(this); diff --git a/rcljava/src/test/java/org/ros2/rcljava/publisher/PublisherTest.java b/rcljava/src/test/java/org/ros2/rcljava/publisher/PublisherTest.java index 336c6204..c4e0bd99 100644 --- a/rcljava/src/test/java/org/ros2/rcljava/publisher/PublisherTest.java +++ b/rcljava/src/test/java/org/ros2/rcljava/publisher/PublisherTest.java @@ -24,6 +24,9 @@ import org.junit.Test; import org.ros2.rcljava.RCLJava; +import org.ros2.rcljava.consumers.Consumer; +import org.ros2.rcljava.events.EventHandler; +import org.ros2.rcljava.events.publisher_statuses.LivelinessLost; import org.ros2.rcljava.node.Node; public class PublisherTest { @@ -63,4 +66,20 @@ public final void testCreateAndDispose() { RCLJava.shutdown(); } + + @Test + public final void testCreateLivelinessLostEvent() { + RCLJava.rclJavaInit(); + Node node = RCLJava.createNode("test_node"); + Publisher publisher = + node.createPublisher(std_msgs.msg.String.class, "test_topic"); + EventHandler eventHandler = publisher.createEventHandler( + LivelinessLost.factory, new Consumer() { + public void accept(final LivelinessLost status) {} + } + ); + assertNotEquals(0, eventHandler.getHandle()); + RCLJava.shutdown(); + assertEquals(0, eventHandler.getHandle()); + } }