-
Notifications
You must be signed in to change notification settings - Fork 99
New issue
Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.
By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.
Already on GitHub? Sign in to your account
Gamecontroller can Bind to a Different Port Based on Command Line Argument #3207
Gamecontroller can Bind to a Different Port Based on Command Line Argument #3207
Conversation
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I just tried this, and I'm not sure if it works as expected. Trying to launch 2+ concurrent AI vs AIs causes a crash due to a port binding error.
In the example you've listed in your PR description, since one of the targets you're running is running with bazel test
with the "exclusive"
tag added to its build file, the test actually ends up finishing before thunderscope
is launched. A longtime issue we've had is actually #2619 which is similar to the problem I've described.
This is somewhat of a different, but very related, problem to #3160. Above mainly discusses an issue where we all of our simulated pytests need to have the "exclusive"
tag to avoid having multiple of them running concurrently and the gamecontroller port being busy (by default Bazel runs tests in parallel). Fixing this issue will likely help speed up our CI/CD workflow for simulated pytests.
On the other hand, #3160 discusses the problem we faced during Robocup where most teams connect their computer to the same network which has access to the internet. Furthermore, during a game or field testing, all teams on the field connect to the same LAN. Our goal is to basically almost always launch the gamecontroller on some non-official port/multicast IP address that other teams or other members of our team aren't listening to for gamecontroller commands. This will allow us to send gamecontroller commands that only effects the fullsystem being ran locally. This should be the case UNLESS we're playing an actual game where we're not launching our own gamecontroller locally and we're using the provided field gamecontroller. Hence #3161 discussing a new "robocup" mode used during actual games with a non-local gc. To achieve this, we will probably need to update RobotCommunication
aswell to not always listen to the official gamecontroller/referee port/IP:
Software/src/software/thunderscope/robot_communication.py
Lines 114 to 119 in aaa16cb
self.receive_ssl_referee_proto = tbots_cpp.SSLRefereeProtoListener( | |
SSL_REFEREE_ADDRESS, | |
SSL_REFEREE_PORT, | |
lambda data: self.current_proto_unix_io.send_proto(Referee, data), | |
True, | |
) |
Let me know if this doesn't make sense or you want any clarifications. Tagging @itsarune in case I've missed anything.
On a separate note, could you also update this socket connection to have a more descriptive error message:
Software/src/software/networking/ssl_proto_communication.py
Lines 37 to 39 in 1528173
# bind to all local interfaces, TCP | |
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM) | |
self.socket.connect(("", port)) |
For example:
try:
# bind to all local interfaces, TCP
self.socket = socket.socket(socket.AF_INET, socket.SOCK_STREAM)
self.socket.connect(("", port))
except ConnectionRefusedError:
raise ConnectionRefusedError(
f"SSL Socket connection refused on port {port}. Is binary already running in a separate process?"
)
src/software/thunderscope/binary_context_managers/game_controller.py
Outdated
Show resolved
Hide resolved
db5f7e0
to
c052a97
Compare
…_Software into gamecontroller_port
self, | ||
supress_logs: bool = False, | ||
gamecontroller_port: int = None, | ||
referee_addresse: int = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
referee_addresse: int = None, | |
referee_address: int = None, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
bump: looks like it didn't get committed
@nimazareian covered everything, I don't have anything more to add onto nima's conversation |
self.REFEREE_IP = referee_addresse | ||
if referee_addresse is None: | ||
self.REFEREE_IP = "224.5.23.1" | ||
|
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
REFEREE_IP
is supposed to be a constant, we shouldn't be modifying it inside here
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
On the other hand, #3160 discusses the problem we faced during Robocup where most teams connect their computer to the same network which has access to the internet. Furthermore, during a game or field testing, all teams on the field connect to the same LAN. Our goal is to basically almost always launch the gamecontroller on some non-official port/multicast IP address that other teams or other members of our team aren't listening to for gamecontroller commands. This will allow us to send gamecontroller commands that only effects the fullsystem being ran locally. This should be the case UNLESS we're playing an actual game where we're not launching our own gamecontroller locally and we're using the provided field gamecontroller. Hence #3161 discussing a new "robocup" mode used during actual games with a non-local gc. To achieve this, we will probably need to update RobotCommunication aswell to not always listen to the official gamecontroller/referee port/IP
Doesn't this contradicts what nima said? Am I misunderstanding something? Are we setting the port instead then but keeping the multicast address constant?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nima's right but in the implementation, don't modify REFEREE_IP
. That is a constant (which is why it's all capitalized) we use during real games for connecting to the Gamecontroller and we should leave it so that it always point to that ip address. By convention, we never modify constants during runtime.
Instead, use a new local variable:
self.REFEREE_IP = referee_addresse | |
if referee_addresse is None: | |
self.REFEREE_IP = "224.5.23.1" | |
multicast_address = referee_address if referee_address else Gamecontroller.REFEREE_IP | |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Shouldn't this be self.multicast_address
instead of multicast_address
, since we are accessing this variable in line 180?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yeah, you're correct
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good start! left a few comments to try to clean up the logic a bit
…_Software into gamecontroller_port
# this allows gamecontroller to listen to override commands | ||
self.command_override_buffer = ThreadSafeBuffer( | ||
buffer_size=2, protobuf_type=ManualGCCommand | ||
) | ||
|
||
@staticmethod | ||
def get_referee_port_staticmethod(gamecontroller: Gamecontroller): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
def get_referee_port_staticmethod(gamecontroller: Gamecontroller): | |
def get_referee_port(gamecontroller: Gamecontroller): |
it doesn't need to be called staticmethod
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
we kind of need a static method of a function. I feel like a static method would make more sense. see the comments below for more justification.
# this allows gamecontroller to listen to override commands | ||
self.command_override_buffer = ThreadSafeBuffer( | ||
buffer_size=2, protobuf_type=ManualGCCommand | ||
) | ||
|
||
@staticmethod | ||
def get_referee_port_staticmethod(gamecontroller: Gamecontroller): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
why not make it a class function?
def get_referee_port_staticmethod(gamecontroller: Gamecontroller): | |
def get_referee_port(self): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
oh there already is a get_referee_port
. why do we have this function at all?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This function is used situation like the following:
In otherwords, the instance of gamecontroller has type None, but we want a function or staticmethod to return the default port of the referee.
Since the gamecontroller has type None, we cannot call self.get_referee_port, but Gamecontroller.get_referee_port_staticmethod instead. In other words, if the gamecontroller has type None, we want to listen to the default port.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm interesting. In that case I personally would say Gamecontroller.get_referee_port_static
might be a more suited name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have changed the name.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
awesome work! just a minor nit
…_Software into gamecontroller_port
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Could you run fix_formatting.sh
locally, and if it failed to format python files, could you try running our python formatter black
manually? You can find the command for it in the formatting script.
# this allows gamecontroller to listen to override commands | ||
self.command_override_buffer = ThreadSafeBuffer( | ||
buffer_size=2, protobuf_type=ManualGCCommand | ||
) | ||
|
||
@staticmethod | ||
def get_referee_port_staticmethod(gamecontroller: Gamecontroller): |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Hmm interesting. In that case I personally would say Gamecontroller.get_referee_port_static
might be a more suited name.
src/software/thunderscope/binary_context_managers/game_controller.py
Outdated
Show resolved
Hide resolved
src/software/thunderscope/binary_context_managers/game_controller.py
Outdated
Show resolved
Hide resolved
src/software/thunderscope/binary_context_managers/game_controller.py
Outdated
Show resolved
Hide resolved
I've just ran |
src/software/thunderscope/binary_context_managers/game_controller.py
Outdated
Show resolved
Hide resolved
src/software/thunderscope/binary_context_managers/game_controller.py
Outdated
Show resolved
Hide resolved
|
||
self.ci_port = self.next_free_port() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is a duplicated call (same as line 40)
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, locally I tried updating this line so we also use a random port for this as well, but I'm not sure why I'm still unable to to launch two Thunderscopes at once: ./tbots.py run thunderscope --disable_communication --run_diagnostics --run_blue --interface eno1 --launch_gc
This was mainly out of curiosity and isn't something we need to be able to achieve.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
done.
src/software/thunderscope/binary_context_managers/game_controller.py
Outdated
Show resolved
Hide resolved
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Good work wrapping this up! 💯
current_proto_unix_io=friendly_proto_unix_io, | ||
multicast_channel=getRobotMulticastChannel(args.channel), | ||
interface=args.interface, | ||
estop_mode=estop_mode, | ||
estop_path=estop_path, | ||
enable_radio=args.enable_radio, | ||
referee_port=Gamecontroller.get_referee_port_static(gamecontroller), |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
for the future: the better way is to use the python ternary operator:
referee_port=Gamecontroller.get_referee_port_static(gamecontroller), | |
referee_port=gamecontroller.get_referee_port() if gamecontroller else REFEREE_PORT, |
why?
because if you're reading this code, there's an extra level of indirection where you have to peruse the implementation of the Gamecontroller module to figure out the REFEREE_PORT
whereas it's very clear which referee port we bind to in the suggested code. We don't use get_referee_port_static
enough around our codebase for this pattern to simplify the code
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm not going to block merging for this change since you'll lose your approvals but keep it in mind for the future
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This is actually life changing.
I think a lot of the time I need to do something like that, but I just don't know that feature well enough to use it. we could get rid of this weird static method.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also, a lot of the time, I want initialize a variable when the being passed into the function is None.
args = parser.parse_args() | ||
|
||
# we only have --launch_gc parameter but not args.run_yellow and args.run_blue | ||
if not args.run_blue and not args.run_yellow and args.launch_gc: | ||
parser.error("--launch_gc has to be ran with --run_blue argument") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
parser.error("--launch_gc has to be ran with --run_blue argument") | |
parser.error("--launch_gc has to be ran with --run_blue or --run_yellow argument") |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
go ahead and merge, you can address my nits another time
I think the refactor of |
Description
Game controller can now bind on a different port
Testing Done
Ran the following commands:
and when each of the command from above is running, I ran
Resolved Issues
resolves #3160
Length Justification and Key Files to Review
N/A
Review Checklist
It is the reviewers responsibility to also make sure every item here has been covered
.h
file) should have a javadoc style comment at the start of them. For examples, see the functions defined inthunderbots/software/geom
. Similarly, all classes should have an associated Javadoc comment explaining the purpose of the class.TODO
(or similar) statements should either be completed or associated with a github issue