382 lines
12 KiB
C#
382 lines
12 KiB
C#
// Copyright 2019-2021 Robotec.ai.
|
|
//
|
|
// 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.IO;
|
|
using System.Collections.Generic;
|
|
using UnityEngine;
|
|
using UnityEditor;
|
|
using System.Xml;
|
|
|
|
namespace ROS2
|
|
{
|
|
|
|
/// <summary>
|
|
/// An internal class responsible for handling checking, proper initialization and shutdown of ROS2cs,
|
|
/// </summary>
|
|
internal class ROS2ForUnity
|
|
{
|
|
private static bool isInitialized = false;
|
|
private static string ros2ForUnityAssetFolderName = "Ros2ForUnity";
|
|
private XmlDocument ros2csMetadata = new XmlDocument();
|
|
private XmlDocument ros2ForUnityMetadata = new XmlDocument();
|
|
|
|
public enum Platform
|
|
{
|
|
Windows,
|
|
Linux
|
|
}
|
|
|
|
public static Platform GetOS()
|
|
{
|
|
if (Application.platform == RuntimePlatform.LinuxEditor || Application.platform == RuntimePlatform.LinuxPlayer)
|
|
{
|
|
return Platform.Linux;
|
|
}
|
|
else if (Application.platform == RuntimePlatform.WindowsEditor || Application.platform == RuntimePlatform.WindowsPlayer)
|
|
{
|
|
return Platform.Windows;
|
|
}
|
|
else if (Application.platform == RuntimePlatform.Android)
|
|
{
|
|
return Platform.Linux;
|
|
}
|
|
throw new System.NotSupportedException("Only Linux and Windows are supported");
|
|
}
|
|
|
|
private static bool InEditor() {
|
|
return Application.isEditor;
|
|
}
|
|
|
|
private static string GetOSName()
|
|
{
|
|
switch (GetOS())
|
|
{
|
|
case Platform.Linux:
|
|
return "Linux";
|
|
case Platform.Windows:
|
|
return "Windows";
|
|
default:
|
|
throw new System.NotSupportedException("Only Linux and Windows are supported");
|
|
}
|
|
}
|
|
|
|
private string GetEnvPathVariableName()
|
|
{
|
|
string envVariable = "LD_LIBRARY_PATH";
|
|
if (GetOS() == Platform.Windows)
|
|
{
|
|
envVariable = "PATH";
|
|
}
|
|
return envVariable;
|
|
}
|
|
|
|
private string GetEnvPathVariableValue()
|
|
{
|
|
return Environment.GetEnvironmentVariable(GetEnvPathVariableName());
|
|
}
|
|
|
|
public static string GetRos2ForUnityPath()
|
|
{
|
|
char separator = Path.DirectorySeparatorChar;
|
|
string appDataPath = Application.dataPath;
|
|
string pluginPath = appDataPath;
|
|
|
|
if (Application.platform == RuntimePlatform.Android)
|
|
{
|
|
pluginPath = $"{pluginPath}";
|
|
}
|
|
|
|
if (InEditor()) {
|
|
pluginPath += separator + ros2ForUnityAssetFolderName;
|
|
}
|
|
return pluginPath;
|
|
}
|
|
|
|
public static string GetPluginPath()
|
|
{
|
|
char separator = Path.DirectorySeparatorChar;
|
|
string ros2ForUnityPath = GetRos2ForUnityPath();
|
|
string pluginPath = ros2ForUnityPath;
|
|
|
|
pluginPath += separator + "Plugins";
|
|
|
|
if (InEditor()) {
|
|
pluginPath += separator + GetOSName();
|
|
}
|
|
|
|
if (InEditor() || GetOS() == Platform.Windows)
|
|
{
|
|
pluginPath += separator + "x86_64";
|
|
}
|
|
|
|
if (GetOS() == Platform.Windows)
|
|
{
|
|
pluginPath = pluginPath.Replace("/", "\\");
|
|
}
|
|
|
|
return pluginPath;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Function responsible for setting up of environment paths for standalone builds
|
|
/// </summary>
|
|
/// <description>
|
|
/// Note that on Linux, LD_LIBRARY_PATH as used for dlopen() is determined on process start and this change won't
|
|
/// affect it. Ros2 looks for rmw implementation based on this variable (independently) and the change
|
|
/// is effective for this process, however rmw implementation's dependencies itself are loaded by dynamic linker
|
|
/// anyway so setting it for Linux is pointless.
|
|
/// </description>
|
|
private void SetEnvPathVariable()
|
|
{
|
|
string currentPath = GetEnvPathVariableValue();
|
|
string pluginPath = GetPluginPath();
|
|
|
|
char envPathSep = ':';
|
|
if (GetOS() == Platform.Windows)
|
|
{
|
|
envPathSep = ';';
|
|
}
|
|
|
|
Environment.SetEnvironmentVariable(GetEnvPathVariableName(), pluginPath + envPathSep + currentPath);
|
|
}
|
|
|
|
public bool IsStandalone() {
|
|
return Convert.ToBoolean(Convert.ToInt16(GetMetadataValue(ros2csMetadata, "/ros2cs/standalone")));
|
|
}
|
|
|
|
public string GetROSVersion()
|
|
{
|
|
string ros2SourcedCodename = GetROSVersionSourced();
|
|
string ros2FromRos4UMetadata = GetMetadataValue(ros2ForUnityMetadata, "/ros2_for_unity/ros2");
|
|
|
|
// Sourced ROS2 libs takes priority
|
|
if (string.IsNullOrEmpty(ros2SourcedCodename)) {
|
|
return ros2FromRos4UMetadata;
|
|
}
|
|
|
|
return ros2SourcedCodename;
|
|
}
|
|
|
|
/// <summary>
|
|
/// Checks if both ros2cs and ros2-for-unity were build for the same ros version as well as
|
|
/// the current sourced ros version matches ros2cs binaries.
|
|
/// </summary>
|
|
public void CheckIntegrity()
|
|
{
|
|
string ros2SourcedCodename = GetROSVersionSourced();
|
|
string ros2FromRos2csMetadata = GetMetadataValue(ros2csMetadata, "/ros2cs/ros2");
|
|
string ros2FromRos4UMetadata = GetMetadataValue(ros2ForUnityMetadata, "/ros2_for_unity/ros2");
|
|
|
|
if (ros2FromRos4UMetadata != ros2FromRos2csMetadata) {
|
|
Debug.LogError(
|
|
"ROS2 versions in 'ros2cs' and 'ros2-for-unity' metadata files are not the same. " +
|
|
"This is caused by mixing versions/builds. Plugin might not work correctly."
|
|
);
|
|
}
|
|
|
|
if(!IsStandalone() && ros2SourcedCodename != ros2FromRos2csMetadata) {
|
|
Debug.LogError(
|
|
"ROS2 version in 'ros2cs' metadata doesn't match currently sourced version. " +
|
|
"This is caused by mixing versions/builds. Plugin might not work correctly."
|
|
);
|
|
}
|
|
|
|
if (IsStandalone() && !string.IsNullOrEmpty(ros2SourcedCodename)) {
|
|
Debug.LogError(
|
|
"You should not source ROS2 in 'ros2-for-unity' standalone build. " +
|
|
"Plugin might not work correctly."
|
|
);
|
|
}
|
|
}
|
|
|
|
public string GetROSVersionSourced()
|
|
{
|
|
return Environment.GetEnvironmentVariable("ROS_DISTRO");
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if the ros version is supported, only applicable to non-standalone plugin versions
|
|
/// (i. e. without ros2 libraries included in the plugin).
|
|
/// </summary>
|
|
private void CheckROSSupport(string ros2Codename)
|
|
{
|
|
List<string> supportedVersions = new List<string>() { "foxy", "galactic", "humble", "rolling" };
|
|
var supportedVersionsString = String.Join(", ", supportedVersions);
|
|
if (string.IsNullOrEmpty(ros2Codename))
|
|
{
|
|
string errMessage = "No ROS environment sourced. You need to source your ROS2 " + supportedVersionsString
|
|
+ " environment before launching Unity (ROS_DISTRO env variable not found)";
|
|
Debug.LogError(errMessage);
|
|
#if UNITY_EDITOR
|
|
EditorApplication.isPlaying = false;
|
|
throw new System.InvalidOperationException(errMessage);
|
|
#else
|
|
const int ROS_NOT_SOURCED_ERROR_CODE = 33;
|
|
Application.Quit(ROS_NOT_SOURCED_ERROR_CODE);
|
|
#endif
|
|
}
|
|
|
|
if (!supportedVersions.Contains(ros2Codename))
|
|
{
|
|
string errMessage = "Currently sourced ROS version differs from supported one. Sourced: " + ros2Codename
|
|
+ ", supported: " + supportedVersionsString + ".";
|
|
Debug.LogError(errMessage);
|
|
#if UNITY_EDITOR
|
|
EditorApplication.isPlaying = false;
|
|
throw new System.NotSupportedException(errMessage);
|
|
#else
|
|
const int ROS_BAD_VERSION_CODE = 34;
|
|
Application.Quit(ROS_BAD_VERSION_CODE);
|
|
#endif
|
|
} else if (ros2Codename.Equals("rolling") ) {
|
|
Debug.LogWarning("You are using ROS2 rolling version. Bleeding edge version might not work correctly.");
|
|
}
|
|
}
|
|
|
|
private void RegisterCtrlCHandler()
|
|
{
|
|
#if ENABLE_MONO
|
|
// Il2CPP build does not support Console.CancelKeyPress currently
|
|
Console.CancelKeyPress += (sender, eventArgs) => {
|
|
eventArgs.Cancel = true;
|
|
DestroyROS2ForUnity();
|
|
};
|
|
#endif
|
|
}
|
|
|
|
private void ConnectLoggers()
|
|
{
|
|
Ros2csLogger.setCallback(LogLevel.ERROR, Debug.LogError);
|
|
Ros2csLogger.setCallback(LogLevel.WARNING, Debug.LogWarning);
|
|
Ros2csLogger.setCallback(LogLevel.INFO, Debug.Log);
|
|
Ros2csLogger.setCallback(LogLevel.DEBUG, Debug.Log);
|
|
Ros2csLogger.LogLevel = LogLevel.WARNING;
|
|
}
|
|
|
|
private string GetMetadataValue(XmlDocument doc, string valuePath)
|
|
{
|
|
return doc.DocumentElement.SelectSingleNode(valuePath).InnerText;
|
|
}
|
|
|
|
private void LoadMetadata()
|
|
{
|
|
char separator = Path.DirectorySeparatorChar;
|
|
try
|
|
{
|
|
ros2csMetadata.Load(GetPluginPath() + separator + "metadata_ros2cs.xml");
|
|
ros2ForUnityMetadata.Load(GetRos2ForUnityPath() + separator + "metadata_ros2_for_unity.xml");
|
|
}
|
|
catch (System.IO.FileNotFoundException)
|
|
{
|
|
#if UNITY_EDITOR
|
|
var errMessage = "Could not find metadata files.";
|
|
EditorApplication.isPlaying = false;
|
|
throw new System.IO.FileNotFoundException(errMessage);
|
|
#else
|
|
const int NO_METADATA = 1;
|
|
Application.Quit(NO_METADATA);
|
|
#endif
|
|
}
|
|
}
|
|
|
|
internal ROS2ForUnity()
|
|
{
|
|
// Load metadata
|
|
LoadMetadata();
|
|
string currentRos2Version = GetROSVersion();
|
|
string standalone = IsStandalone() ? "standalone" : "non-standalone";
|
|
|
|
// Self checks
|
|
CheckROSSupport(currentRos2Version);
|
|
CheckIntegrity();
|
|
|
|
// Library loading
|
|
if (GetOS() == Platform.Windows) {
|
|
// Windows version can run standalone, modifies PATH to ensure all plugins visibility
|
|
SetEnvPathVariable();
|
|
} else {
|
|
// For foxy, it is necessary to use modified version of librcpputils to resolve custom msgs packages.
|
|
ROS2.GlobalVariables.absolutePath = GetPluginPath() + "/";
|
|
if (currentRos2Version == "foxy") {
|
|
ROS2.GlobalVariables.preloadLibrary = true;
|
|
ROS2.GlobalVariables.preloadLibraryName = "librcpputils.so";
|
|
}
|
|
}
|
|
|
|
// Initialize
|
|
ConnectLoggers();
|
|
Ros2cs.Init();
|
|
RegisterCtrlCHandler();
|
|
|
|
string rmwImpl = Ros2cs.GetRMWImplementation();
|
|
|
|
Debug.Log("ROS2 version: " + currentRos2Version + ". Build type: " + standalone + ". RMW: " + rmwImpl);
|
|
|
|
#if UNITY_EDITOR
|
|
EditorApplication.playModeStateChanged += this.EditorPlayStateChanged;
|
|
EditorApplication.quitting += this.DestroyROS2ForUnity;
|
|
#endif
|
|
isInitialized = true;
|
|
}
|
|
|
|
private static void ThrowIfUninitialized(string callContext)
|
|
{
|
|
if (!isInitialized)
|
|
{
|
|
throw new InvalidOperationException("Ros2 For Unity is not initialized, can't " + callContext);
|
|
}
|
|
}
|
|
|
|
/// <summary>
|
|
/// Check if ROS2 module is properly initialized and no shutdown was called yet
|
|
/// </summary>
|
|
/// <returns>The state of ROS2 module. Should be checked before attempting to create or use pubs/subs</returns>
|
|
public bool Ok()
|
|
{
|
|
if (!isInitialized)
|
|
{
|
|
return false;
|
|
}
|
|
return Ros2cs.Ok();
|
|
}
|
|
|
|
internal void DestroyROS2ForUnity()
|
|
{
|
|
if (isInitialized)
|
|
{
|
|
Debug.Log("Shutting down Ros2 For Unity");
|
|
Ros2cs.Shutdown();
|
|
isInitialized = false;
|
|
}
|
|
}
|
|
|
|
~ROS2ForUnity()
|
|
{
|
|
DestroyROS2ForUnity();
|
|
}
|
|
|
|
#if UNITY_EDITOR
|
|
void EditorPlayStateChanged(PlayModeStateChange change)
|
|
{
|
|
if (change == PlayModeStateChange.ExitingPlayMode)
|
|
{
|
|
DestroyROS2ForUnity();
|
|
}
|
|
}
|
|
#endif
|
|
}
|
|
|
|
} // namespace ROS2
|