Skip to content
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

Merged
merged 33 commits into from
Jul 10, 2024

Conversation

Mr-Anyone
Copy link
Contributor

Description

Game controller can now bind on a different port

Testing Done

Ran the following commands:

./tbots.py test kickoff_play_test --gamecontroller_port 50000 --enable_thunderscope
./tbots.py run thunderscope --gamecontroller_port 50001 

and when each of the command from above is running, I ran

ps aux | grep gamecontroller

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

  • Function & Class comments: All function definitions (usually in the .h file) should have a javadoc style comment at the start of them. For examples, see the functions defined in thunderbots/software/geom. Similarly, all classes should have an associated Javadoc comment explaining the purpose of the class.
  • Remove all commented out code
  • Remove extra print statements: for example, those just used for testing
  • Resolve all TODO's: All TODO (or similar) statements should either be completed or associated with a github issue

Copy link
Contributor

@nimazareian nimazareian left a 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:

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:

# 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?"
            )

self,
supress_logs: bool = False,
gamecontroller_port: int = None,
referee_addresse: int = None,
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
referee_addresse: int = None,
referee_address: int = None,

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Contributor

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

@itsarune
Copy link
Contributor

@nimazareian covered everything, I don't have anything more to add onto nima's conversation

Comment on lines 48 to 51
self.REFEREE_IP = referee_addresse
if referee_addresse is None:
self.REFEREE_IP = "224.5.23.1"

Copy link
Contributor

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

Copy link
Contributor Author

@Mr-Anyone Mr-Anyone May 31, 2024

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?

Copy link
Contributor

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:

Suggested change
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

Copy link
Contributor Author

@Mr-Anyone Mr-Anyone Jun 1, 2024

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?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Yeah, you're correct

Copy link
Contributor

@itsarune itsarune left a 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

# 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):
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
def get_referee_port_staticmethod(gamecontroller: Gamecontroller):
def get_referee_port(gamecontroller: Gamecontroller):

it doesn't need to be called staticmethod

Copy link
Contributor Author

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):
Copy link
Contributor

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?

Suggested change
def get_referee_port_staticmethod(gamecontroller: Gamecontroller):
def get_referee_port(self):

Copy link
Contributor

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?

Copy link
Contributor Author

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:

https://github.com/Mr-Anyone/Thunderbot_Software/blob/cf668bfeaff698097aadce1d01c60b4c731322c6/src/software/thunderscope/thunderscope_main.py#L322.

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.

Copy link
Contributor

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.

Copy link
Contributor Author

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.

Copy link
Contributor

@itsarune itsarune left a 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

@Mr-Anyone Mr-Anyone requested a review from itsarune July 2, 2024 09:50
Copy link
Contributor

@nimazareian nimazareian left a 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):
Copy link
Contributor

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.

@Mr-Anyone
Copy link
Contributor Author

I've just ran fix_formatting.sh. It seems that my system is missing an dynamic library libtinfo-dev. This is solved through apt-get.


self.ci_port = self.next_free_port()
Copy link
Contributor

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)

Copy link
Contributor

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.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

done.

Copy link
Contributor

@nimazareian nimazareian left a 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! 💯

@nimazareian nimazareian added the Robocup-2024 High priority for Rbocup 2024 label Jul 9, 2024
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),
Copy link
Contributor

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:

Suggested change
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

Copy link
Contributor

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

Copy link
Contributor Author

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.

Copy link
Contributor Author

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")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Suggested change
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")

Copy link
Contributor

@itsarune itsarune left a 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

@Mr-Anyone
Copy link
Contributor Author

I think the refactor of Gamecontroller.get_referee_port_staticmethod could be a good first issue.

@Mr-Anyone Mr-Anyone merged commit 8e45ea0 into UBC-Thunderbots:master Jul 10, 2024
6 checks passed
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
Robocup-2024 High priority for Rbocup 2024
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Ability to Launch Gamecontroller on a custom port
4 participants