Skip to content

Commit

Permalink
guard conditions: add guard conditions
Browse files Browse the repository at this point in the history
  • Loading branch information
hoffmann-stefan committed Jun 17, 2022
1 parent 093c045 commit b7713c5
Show file tree
Hide file tree
Showing 10 changed files with 429 additions and 28 deletions.
4 changes: 4 additions & 0 deletions rcldotnet/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -29,13 +29,15 @@ set(CSHARP_TARGET_FRAMEWORK "netstandard2.0")

set(CS_SOURCES
Client.cs
GuardCondition.cs
MessageStaticMemberCache.cs
Node.cs
Publisher.cs
RCLdotnet.cs
RCLExceptionHelper.cs
RCLRet.cs
SafeClientHandle.cs
SafeGuardConditionHandle.cs
SafeNodeHandle.cs
SafePublisherHandle.cs
SafeRequestIdHandle.cs
Expand Down Expand Up @@ -64,6 +66,7 @@ ament_export_assemblies_dll("lib/${PROJECT_NAME}/dotnet/${PROJECT_NAME}_assembli

add_library(${PROJECT_NAME}_native SHARED
rcldotnet_client.c
rcldotnet_guard_condition.c
rcldotnet_node.c
rcldotnet_publisher.c
rcldotnet.c
Expand Down Expand Up @@ -108,6 +111,7 @@ if(BUILD_TESTING)

add_dotnet_test(test_messages
${CS_SOURCES}
test/test_guard_conditions.cs
test/test_messages.cs
test/test_services.cs
INCLUDE_DLLS
Expand Down
71 changes: 71 additions & 0 deletions rcldotnet/GuardCondition.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
/* Copyright 2022 Stefan Hoffmann <[email protected]>
*
* 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.
*/

using System;
using System.Runtime.InteropServices;
using ROS2.Utils;

namespace ROS2
{
internal static class GuardConditionDelegates
{
internal static readonly DllLoadUtils _dllLoadUtils;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate RCLRet NativeRCLTriggerGuardConditionType(
SafeGuardConditionHandle guardConditionHandle);

internal static NativeRCLTriggerGuardConditionType native_rcl_trigger_guard_condition = null;

static GuardConditionDelegates()
{
_dllLoadUtils = DllLoadUtilsFactory.GetDllLoadUtils();
IntPtr nativeLibrary = _dllLoadUtils.LoadLibrary("rcldotnet");

IntPtr native_rcl_trigger_guard_condition_ptr = _dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_trigger_guard_condition");
GuardConditionDelegates.native_rcl_trigger_guard_condition = (NativeRCLTriggerGuardConditionType)Marshal.GetDelegateForFunctionPointer(
native_rcl_trigger_guard_condition_ptr, typeof(NativeRCLTriggerGuardConditionType));
}
}

public sealed class GuardCondition
{
private readonly Action _callback;

internal GuardCondition(SafeGuardConditionHandle handle, Action callback)
{
Handle = handle;
_callback = callback;
}

// GuardCondition does intentionally (for now) not implement IDisposable as this
// needs some extra consideration how the type works after its
// internal handle is disposed.
// By relying on the GC/Finalizer of SafeHandle the handle only gets
// Disposed if the publisher is not live anymore.
internal SafeGuardConditionHandle Handle { get; }

public void Trigger()
{
RCLRet ret = GuardConditionDelegates.native_rcl_trigger_guard_condition(Handle);
RCLExceptionHelper.CheckReturnValue(ret);
}

internal void TriggerCallback()
{
_callback();
}
}
}
21 changes: 20 additions & 1 deletion rcldotnet/Node.cs
Original file line number Diff line number Diff line change
Expand Up @@ -137,19 +137,21 @@ static NodeDelegates()

public sealed class Node
{

private readonly IList<Subscription> _subscriptions;

private readonly IList<Service> _services;

private readonly IList<Client> _clients;

private readonly IList<GuardCondition> _guardConditions;

internal Node(SafeNodeHandle handle)
{
Handle = handle;
_subscriptions = new List<Subscription>();
_services = new List<Service>();
_clients = new List<Client>();
_guardConditions = new List<GuardCondition>();
}

public IList<Subscription> Subscriptions => _subscriptions;
Expand All @@ -161,6 +163,8 @@ internal Node(SafeNodeHandle handle)

public IList<Client> Clients => _clients;

public IList<GuardCondition> GuardConditions => _guardConditions;

// Node does intentionaly (for now) not implement IDisposable as this
// needs some extra consideration how the type works after its
// internal handle is disposed.
Expand Down Expand Up @@ -248,5 +252,20 @@ public Client<TService, TRequest, TResponse> CreateClient<TService, TRequest, TR
_clients.Add(client);
return client;
}

public GuardCondition CreateGuardCondition(Action callback)
{
var guardConditionHandle = new SafeGuardConditionHandle();
RCLRet ret = RCLdotnetDelegates.native_rcl_create_guard_condition_handle(ref guardConditionHandle);
if (ret != RCLRet.Ok)
{
guardConditionHandle.Dispose();
throw RCLExceptionHelper.CreateFromReturnValue(ret, $"{nameof(RCLdotnetDelegates.native_rcl_create_guard_condition_handle)}() failed.");
}

var guardCondition = new GuardCondition(guardConditionHandle, callback);
_guardConditions.Add(guardCondition);
return guardCondition;
}
}
}
120 changes: 94 additions & 26 deletions rcldotnet/RCLdotnet.cs
Original file line number Diff line number Diff line change
Expand Up @@ -56,6 +56,18 @@ internal delegate RCLRet NativeRCLDestroyNodeHandleType(

internal static NativeRCLDestroyNodeHandleType native_rcl_destroy_node_handle = null;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate RCLRet NativeRCLCreateGuardConditionHandleType(
ref SafeGuardConditionHandle guardConditionHandle);

internal static NativeRCLCreateGuardConditionHandleType native_rcl_create_guard_condition_handle = null;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate RCLRet NativeRCLDestroyGuardConditionHandleType(
IntPtr guardConditionHandle);

internal static NativeRCLDestroyGuardConditionHandleType native_rcl_destroy_guard_condition_handle = null;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate IntPtr NativeRCLGetRMWIdentifierType();

Expand Down Expand Up @@ -94,11 +106,21 @@ internal delegate RCLRet NativeRCLCreateWaitSetHandleType(

internal static NativeRCLWaitSetAddClientType native_rcl_wait_set_add_client = null;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate RCLRet NativeRCLWaitSetAddGuardConditionType(SafeWaitSetHandle waitSetHandle, SafeGuardConditionHandle guardConditionHandle);

internal static NativeRCLWaitSetAddGuardConditionType native_rcl_wait_set_add_guard_condition = null;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate RCLRet NativeRCLWaitType(SafeWaitSetHandle waitSetHandle, long timeout);

internal static NativeRCLWaitType native_rcl_wait = null;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate bool NativeRCLWaitSetGuardConditionReady(SafeWaitSetHandle waitSetHandle, int index);

internal static NativeRCLWaitSetGuardConditionReady native_rcl_wait_set_guard_condition_ready = null;

[UnmanagedFunctionPointer(CallingConvention.Cdecl)]
internal delegate RCLRet NativeRCLTakeType(SafeSubscriptionHandle subscriptionHandle, SafeHandle messageHandle);

Expand Down Expand Up @@ -182,6 +204,18 @@ static RCLdotnetDelegates()
(NativeRCLDestroyNodeHandleType)Marshal.GetDelegateForFunctionPointer(
native_rcl_destroy_node_handle_ptr, typeof(NativeRCLDestroyNodeHandleType));

IntPtr native_rcl_create_guard_condition_handle_ptr =
_dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_create_guard_condition_handle");
RCLdotnetDelegates.native_rcl_create_guard_condition_handle =
(NativeRCLCreateGuardConditionHandleType)Marshal.GetDelegateForFunctionPointer(
native_rcl_create_guard_condition_handle_ptr, typeof(NativeRCLCreateGuardConditionHandleType));

IntPtr native_rcl_destroy_guard_condition_handle_ptr =
_dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_destroy_guard_condition_handle");
RCLdotnetDelegates.native_rcl_destroy_guard_condition_handle =
(NativeRCLDestroyGuardConditionHandleType)Marshal.GetDelegateForFunctionPointer(
native_rcl_destroy_guard_condition_handle_ptr, typeof(NativeRCLDestroyGuardConditionHandleType));

IntPtr native_rcl_create_wait_set_handle_ptr =
_dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_create_wait_set_handle");
RCLdotnetDelegates.native_rcl_create_wait_set_handle =
Expand Down Expand Up @@ -218,12 +252,24 @@ static RCLdotnetDelegates()
(NativeRCLWaitSetAddClientType)Marshal.GetDelegateForFunctionPointer(
native_rcl_wait_set_add_client_ptr, typeof(NativeRCLWaitSetAddClientType));

IntPtr native_rcl_wait_set_add_guard_condition_ptr =
_dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_wait_set_add_guard_condition");
RCLdotnetDelegates.native_rcl_wait_set_add_guard_condition =
(NativeRCLWaitSetAddGuardConditionType)Marshal.GetDelegateForFunctionPointer(
native_rcl_wait_set_add_guard_condition_ptr, typeof(NativeRCLWaitSetAddGuardConditionType));

IntPtr native_rcl_wait_ptr =
_dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_wait");
RCLdotnetDelegates.native_rcl_wait =
(NativeRCLWaitType)Marshal.GetDelegateForFunctionPointer(
native_rcl_wait_ptr, typeof(NativeRCLWaitType));

IntPtr native_rcl_wait_set_guard_condition_ready_ptr =
_dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_wait_set_guard_condition_ready");
RCLdotnetDelegates.native_rcl_wait_set_guard_condition_ready =
(NativeRCLWaitSetGuardConditionReady)Marshal.GetDelegateForFunctionPointer(
native_rcl_wait_set_guard_condition_ready_ptr, typeof(NativeRCLWaitSetGuardConditionReady));

IntPtr native_rcl_take_ptr =
_dllLoadUtils.GetProcAddress(nativeLibrary, "native_rcl_take");
RCLdotnetDelegates.native_rcl_take =
Expand Down Expand Up @@ -350,6 +396,12 @@ private static void WaitSetAddClient(SafeWaitSetHandle waitSetHandle, SafeClient
RCLExceptionHelper.CheckReturnValue(ret, $"{nameof(RCLdotnetDelegates.native_rcl_wait_set_add_client)}() failed.");
}

private static void WaitSetAddGuardCondition(SafeWaitSetHandle waitSetHandle, SafeGuardConditionHandle guardConditionHandle)
{
RCLRet ret = RCLdotnetDelegates.native_rcl_wait_set_add_guard_condition(waitSetHandle, guardConditionHandle);
RCLExceptionHelper.CheckReturnValue(ret, $"{nameof(RCLdotnetDelegates.native_rcl_wait_set_add_guard_condition)}() failed.");
}

/// <summary>
/// Block until the wait set is ready or until the timeout has been exceeded.
/// </summary>
Expand Down Expand Up @@ -531,7 +583,7 @@ private static void SendResponse(Service service, SafeRequestIdHandle requestHea
public static void SpinOnce(Node node, long timeout)
{
int numberOfSubscriptions = node.Subscriptions.Count;
int numberOfGuardConditions = 0;
int numberOfGuardConditions = node.GuardConditions.Count;
int numberOfTimers = 0;
int numberOfClients = node.Clients.Count;
int numberOfServices = node.Services.Count;
Expand Down Expand Up @@ -577,50 +629,66 @@ public static void SpinOnce(Node node, long timeout)
WaitSetAddClient(waitSetHandle, client.Handle);
}

foreach (var guardCondition in node.GuardConditions)
{
WaitSetAddGuardCondition(waitSetHandle, guardCondition.Handle);
}

bool ready = Wait(waitSetHandle, timeout);
if (!ready)
{
return; // timeout
}
}

foreach (Subscription subscription in node.Subscriptions)
{
IRosMessage message = subscription.CreateMessage();
bool result = Take(subscription, message);
if (result)
foreach (Subscription subscription in node.Subscriptions)
{
subscription.TriggerCallback(message);
IRosMessage message = subscription.CreateMessage();
bool result = Take(subscription, message);
if (result)
{
subscription.TriggerCallback(message);
}
}
}

// requestIdHandle gets reused for each element in the loop.
using (SafeRequestIdHandle requestIdHandle = CreateRequestId())
{
foreach (var service in node.Services)
// requestIdHandle gets reused for each element in the loop.
using (SafeRequestIdHandle requestIdHandle = CreateRequestId())
{
var request = service.CreateRequest();
var response = service.CreateResponse();
foreach (var service in node.Services)
{
var request = service.CreateRequest();
var response = service.CreateResponse();

var result = TakeRequest(service, requestIdHandle, request);
if (result)
var result = TakeRequest(service, requestIdHandle, request);
if (result)
{
service.TriggerCallback(request, response);

SendResponse(service, requestIdHandle, response);
}
}

foreach (var client in node.Clients)
{
service.TriggerCallback(request, response);
var response = client.CreateResponse();

SendResponse(service, requestIdHandle, response);
var result = TakeResponse(client, requestIdHandle, response);
if (result)
{
var sequenceNumber = RCLdotnetDelegates.native_rcl_request_id_get_sequence_number(requestIdHandle);
client.HandleResponse(sequenceNumber, response);
}
}
}

foreach (var client in node.Clients)
int index = 0;
foreach (GuardCondition guardCondition in node.GuardConditions)
{
var response = client.CreateResponse();

var result = TakeResponse(client, requestIdHandle, response);
if (result)
if (RCLdotnetDelegates.native_rcl_wait_set_guard_condition_ready(waitSetHandle, index))
{
var sequenceNumber = RCLdotnetDelegates.native_rcl_request_id_get_sequence_number(requestIdHandle);
client.HandleResponse(sequenceNumber, response);
guardCondition.TriggerCallback();
}

index++;
}
}
}
Expand Down
41 changes: 41 additions & 0 deletions rcldotnet/SafeGuardConditionHandle.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
/* Copyright 2022 Stefan Hoffmann <[email protected]>
*
* 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.
*/

using System.Diagnostics;
using Microsoft.Win32.SafeHandles;

namespace ROS2
{
/// <summary>
/// Safe handle representing a rcl_guard_condition_t
/// </summary>
internal sealed class SafeGuardConditionHandle : SafeHandleZeroOrMinusOneIsInvalid
{
public SafeGuardConditionHandle()
: base(ownsHandle: true)
{
}

protected override bool ReleaseHandle()
{
RCLRet ret = RCLdotnetDelegates.native_rcl_destroy_guard_condition_handle(handle);
bool successfullyFreed = ret == RCLRet.Ok;

Debug.Assert(successfullyFreed);

return successfullyFreed;
}
}
}
Loading

0 comments on commit b7713c5

Please sign in to comment.