Skip to content

Commit fe6a63f

Browse files
surfnerdChris Elion
andauthored
Use SemVer to check communication compatibility between C# and Python (#3760)
* [communication] Use semantic versioning to test communication compatibility between C# and Python. - Add tests for the change. Co-authored-by: Chris Elion <[email protected]>
1 parent b6759f1 commit fe6a63f

File tree

7 files changed

+188
-19
lines changed

7 files changed

+188
-19
lines changed

com.unity.ml-agents/CHANGELOG.md

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,7 @@ and this project adheres to [Semantic Versioning](http://semver.org/spec/v2.0.0.
1717
- Added ability to start training (initialize model weights) from a previous run ID. (#3710)
1818
- The internal event `Academy.AgentSetStatus` was renamed to `Academy.AgentPreStep` and made public.
1919
- The offset logic was removed from DecisionRequester.
20+
- The communication API version has been bumped up to 1.0.0 and will use [Semantic Versioning](https://semver.org/) to do compatibility checks for communication between Unity and the Python process.
2021

2122
### Minor Changes
2223
- Format of console output has changed slightly and now matches the name of the model/summary directory. (#3630, #3616)

com.unity.ml-agents/Runtime/Communicator/RpcCommunicator.cs

Lines changed: 46 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -61,6 +61,41 @@ public RpcCommunicator(CommunicatorInitParameters communicatorInitParameters)
6161

6262
#region Initialization
6363

64+
internal static bool CheckCommunicationVersionsAreCompatible(
65+
string unityCommunicationVersion,
66+
string pythonApiVersion,
67+
string pythonLibraryVersion)
68+
{
69+
var unityVersion = new Version(unityCommunicationVersion);
70+
var pythonVersion = new Version(pythonApiVersion);
71+
if (unityVersion.Major == 0)
72+
{
73+
if (unityVersion.Major != pythonVersion.Major || unityVersion.Minor != pythonVersion.Minor)
74+
{
75+
return false;
76+
}
77+
78+
}
79+
else if (unityVersion.Major != pythonVersion.Major)
80+
{
81+
return false;
82+
}
83+
else if (unityVersion.Minor != pythonVersion.Minor)
84+
{
85+
// Even if we initialize, we still want to check to make sure that we inform users of minor version
86+
// changes. This will surface any features that may not work due to minor version incompatibilities.
87+
Debug.LogWarningFormat(
88+
"WARNING: The communication API versions between Unity and python differ at the minor version level. " +
89+
"Python API: {0}, Unity API: {1} Python Library Version: {2} .\n" +
90+
"This means that some features may not work unless you upgrade the package with the lower version." +
91+
"Please find the versions that work best together from our release page.\n" +
92+
"https://github.com/Unity-Technologies/ml-agents/releases",
93+
pythonApiVersion, unityCommunicationVersion, pythonLibraryVersion
94+
);
95+
}
96+
return true;
97+
}
98+
6499
/// <summary>
65100
/// Sends the initialization parameters through the Communicator.
66101
/// Is used by the academy to send initialization parameters to the communicator.
@@ -87,17 +122,23 @@ public UnityRLInitParameters Initialize(CommunicatorInitParameters initParameter
87122
},
88123
out input);
89124

125+
var pythonCommunicationVersion = initializationInput.RlInitializationInput.CommunicationVersion;
126+
var pythonPackageVersion = initializationInput.RlInitializationInput.PackageVersion;
127+
var unityCommunicationVersion = initParameters.unityCommunicationVersion;
128+
129+
var communicationIsCompatible = CheckCommunicationVersionsAreCompatible(unityCommunicationVersion,
130+
pythonCommunicationVersion,
131+
pythonPackageVersion);
132+
90133
// Initialization succeeded part-way. The most likely cause is a mismatch between the communicator
91134
// API strings, so log an explicit warning if that's the case.
92135
if (initializationInput != null && input == null)
93136
{
94-
var pythonCommunicationVersion = initializationInput.RlInitializationInput.CommunicationVersion;
95-
var pythonPackageVersion = initializationInput.RlInitializationInput.PackageVersion;
96-
if (pythonCommunicationVersion != initParameters.unityCommunicationVersion)
137+
if (!communicationIsCompatible)
97138
{
98139
Debug.LogWarningFormat(
99-
"Communication protocol between python ({0}) and Unity ({1}) don't match. " +
100-
"Python library version: {2}.",
140+
"Communication protocol between python ({0}) and Unity ({1}) have different " +
141+
"versions which make them incompatible. Python library version: {2}.",
101142
pythonCommunicationVersion, initParameters.unityCommunicationVersion,
102143
pythonPackageVersion
103144
);

com.unity.ml-agents/Tests/Editor/Communicator.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.
Lines changed: 55 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,55 @@
1+
using System;
2+
using System.Collections;
3+
using System.Text.RegularExpressions;
4+
using NUnit.Framework;
5+
using UnityEngine;
6+
using UnityEngine.TestTools;
7+
8+
namespace MLAgents.Tests.Communicator
9+
{
10+
[TestFixture]
11+
public class RpcCommunicatorTests
12+
{
13+
14+
[Test]
15+
public void TestCheckCommunicationVersionsAreCompatible()
16+
{
17+
var unityVerStr = "1.0.0";
18+
var pythonVerStr = "1.0.0";
19+
var pythonPackageVerStr = "0.16.0";
20+
21+
Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
22+
pythonVerStr,
23+
pythonPackageVerStr));
24+
LogAssert.NoUnexpectedReceived();
25+
26+
pythonVerStr = "1.1.0";
27+
Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
28+
pythonVerStr,
29+
pythonPackageVerStr));
30+
31+
// Ensure that a warning was printed.
32+
LogAssert.Expect(LogType.Warning, new Regex("(.\\s)+"));
33+
34+
unityVerStr = "2.0.0";
35+
Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
36+
pythonVerStr,
37+
pythonPackageVerStr));
38+
39+
unityVerStr = "0.15.0";
40+
pythonVerStr = "0.15.0";
41+
Assert.IsTrue(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
42+
pythonVerStr,
43+
pythonPackageVerStr));
44+
unityVerStr = "0.16.0";
45+
Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
46+
pythonVerStr,
47+
pythonPackageVerStr));
48+
unityVerStr = "1.15.0";
49+
Assert.IsFalse(RpcCommunicator.CheckCommunicationVersionsAreCompatible(unityVerStr,
50+
pythonVerStr,
51+
pythonPackageVerStr));
52+
53+
}
54+
}
55+
}

com.unity.ml-agents/Tests/Editor/Communicator/RpcCommunicatorTests.cs.meta

Lines changed: 3 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

ml-agents-envs/mlagents_envs/environment.py

Lines changed: 49 additions & 14 deletions
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,5 @@
11
import atexit
2+
from distutils.version import StrictVersion
23
import glob
34
import uuid
45
import numpy as np
@@ -45,7 +46,6 @@
4546
import signal
4647
import struct
4748

48-
4949
logger = get_logger(__name__)
5050

5151

@@ -71,6 +71,47 @@ class UnityEnvironment(BaseEnv):
7171
# Command line argument used to pass the port to the executable environment.
7272
PORT_COMMAND_LINE_ARG = "--mlagents-port"
7373

74+
@staticmethod
75+
def _raise_version_exception(unity_com_ver: str) -> None:
76+
raise UnityEnvironmentException(
77+
f"The communication API version is not compatible between Unity and python. "
78+
f"Python API: {UnityEnvironment.API_VERSION}, Unity API: {unity_com_ver}.\n "
79+
f"Please go to https://github.com/Unity-Technologies/ml-agents/releases/tag/latest_release "
80+
f"to download the latest version of ML-Agents."
81+
)
82+
83+
@staticmethod
84+
def check_communication_compatibility(
85+
unity_com_ver: str, python_api_version: str, unity_package_version: str
86+
) -> bool:
87+
unity_communicator_version = StrictVersion(unity_com_ver)
88+
api_version = StrictVersion(python_api_version)
89+
if unity_communicator_version.version[0] == 0:
90+
if (
91+
unity_communicator_version.version[0] != api_version.version[0]
92+
or unity_communicator_version.version[1] != api_version.version[1]
93+
):
94+
# Minor beta versions differ.
95+
return False
96+
elif unity_communicator_version.version[0] != api_version.version[0]:
97+
# Major versions mismatch.
98+
return False
99+
elif unity_communicator_version.version[1] != api_version.version[1]:
100+
# Non-beta minor versions mismatch. Log a warning but allow execution to continue.
101+
logger.warning(
102+
f"WARNING: The communication API versions between Unity and python differ at the minor version level. "
103+
f"Python API: {python_api_version}, Unity API: {unity_communicator_version}.\n"
104+
f"This means that some features may not work unless you upgrade the package with the lower version."
105+
f"Please find the versions that work best together from our release page.\n"
106+
"https://github.com/Unity-Technologies/ml-agents/releases"
107+
)
108+
else:
109+
logger.info(
110+
f"Connected to Unity environment with package version {unity_package_version} "
111+
f"and communication version {unity_com_ver}"
112+
)
113+
return True
114+
74115
def __init__(
75116
self,
76117
file_name: Optional[str] = None,
@@ -153,20 +194,14 @@ def __init__(
153194
self._close(0)
154195
raise
155196

156-
unity_communicator_version = aca_params.communication_version
157-
if unity_communicator_version != UnityEnvironment.API_VERSION:
197+
if not UnityEnvironment.check_communication_compatibility(
198+
aca_params.communication_version,
199+
UnityEnvironment.API_VERSION,
200+
aca_params.package_version,
201+
):
158202
self._close(0)
159-
raise UnityEnvironmentException(
160-
f"The communication API version is not compatible between Unity and python. "
161-
f"Python API: {UnityEnvironment.API_VERSION}, Unity API: {unity_communicator_version}.\n "
162-
f"Please go to https://github.com/Unity-Technologies/ml-agents/releases/tag/latest_release "
163-
f"to download the latest version of ML-Agents."
164-
)
165-
else:
166-
logger.info(
167-
f"Connected to Unity environment with package version {aca_params.package_version} "
168-
f"and communication version {aca_params.communication_version}"
169-
)
203+
UnityEnvironment._raise_version_exception(aca_params.communication_version)
204+
170205
self._env_state: Dict[str, Tuple[DecisionSteps, TerminalSteps]] = {}
171206
self._env_specs: Dict[str, BehaviorSpec] = {}
172207
self._env_actions: Dict[str, np.ndarray] = {}

ml-agents-envs/mlagents_envs/tests/test_envs.py

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -122,6 +122,37 @@ def test_close(mock_communicator, mock_launcher):
122122
assert comm.has_been_closed
123123

124124

125+
def test_check_communication_compatibility():
126+
unity_ver = "1.0.0"
127+
python_ver = "1.0.0"
128+
unity_package_version = "0.15.0"
129+
assert UnityEnvironment.check_communication_compatibility(
130+
unity_ver, python_ver, unity_package_version
131+
)
132+
unity_ver = "1.1.0"
133+
assert UnityEnvironment.check_communication_compatibility(
134+
unity_ver, python_ver, unity_package_version
135+
)
136+
unity_ver = "2.0.0"
137+
assert not UnityEnvironment.check_communication_compatibility(
138+
unity_ver, python_ver, unity_package_version
139+
)
140+
141+
unity_ver = "0.16.0"
142+
python_ver = "0.16.0"
143+
assert UnityEnvironment.check_communication_compatibility(
144+
unity_ver, python_ver, unity_package_version
145+
)
146+
unity_ver = "0.17.0"
147+
assert not UnityEnvironment.check_communication_compatibility(
148+
unity_ver, python_ver, unity_package_version
149+
)
150+
unity_ver = "1.16.0"
151+
assert not UnityEnvironment.check_communication_compatibility(
152+
unity_ver, python_ver, unity_package_version
153+
)
154+
155+
125156
def test_returncode_to_signal_name():
126157
assert UnityEnvironment.returncode_to_signal_name(-2) == "SIGINT"
127158
assert UnityEnvironment.returncode_to_signal_name(42) is None

0 commit comments

Comments
 (0)