Subsystem Singletons

So I was playing around with Kotlin the other day and noticed that they have a type specifically for the Singleton pattern.

Assuming everything is programmed correctly on the Singleton end is there any issue using them in production robot code. I’ve done work with the simulator, but wasn’t sure if it was worth it.

I love using singletons for robot code. You could probably get away without them, but it makes accessing your subsystems from commands really simple with a static getter.

1 Like

I generally only use the singleton pattern when I need to access an object in many places, and it would be inconvenient or messy to pass said object into all those places.

So for subsystems I don’t generally use the singleton pattern, because they’re really only used in RobotContainer.java, and if I need to create a custom command class, I make the constructor for that class take in the subsystems that it needs, so again, no need for the singleton pattern.

It is important to know when/where to use certain patterns.

2 Likes

Refraining from singleton patterns can help in the case of needing to comment out whole subsystems (which we’ve had to do…). Rather than comment every instance of your subsystem’s singleton, simply passing the subsystem around will allow you to comment it out only at its source and nothing more, and end up with that subsystem having no behavior thereafter.

Also, most of this is moot anyway if every command is constructed within the subsystem itself. However, multi-subsystem commands still need custom command classes, in which case passing the subsystems is perfectly acceptable.

1 Like

This isn’t quite true; you can write a multi subsystem composition as a factory method in RobotContainer.

4 Likes

So my subsystems look like this

static constants inner class to house constants. I don’t like using the Constants file next to RobotContainer. I have to search forever to find stuff.

Then fields
Then no arg constructor that creates the fields
public Telemetery methods, getters and resetters
private control methods that the commands use
public non-static command creaters. i.e. createSetElevatorToHeightCommand(double heightMeters), and one that handles manual control

periodic

initSendable for SmartDashboard ease of use.

So all I have to do in RobotContainer is send subsystem to dashboard, set default command, set any other commands.

Then for multi subsystem commands I compose those together in the configure binding area for manual and create separate static factory classes for autos.

1 Like

This sounds like a reasonable project structure.

I discourage the use of singletons in general because I don’t think global state is a very sound pattern. For classes that are universal and effectively stateless, singletons can make sense - but I do not think robot subsystems are an appropriate application, especially because if you use functional patterns there’s almost no manual injection to avoid (capture semantics handle it all for you).

4 Likes

Oh that’s true. While I haven’t done that outside of auton command composition, I have found that generally more complex multi-subsystem commands work better as command classes. Then again, I’m sure it varies person by person.

2 Likes

Yeah, our team uses kotlin and we switched to using objects(singletons) for the 2023 offseason for subsystems.

Big heads up though: objects are LAZILY initialized, which means you have to reference them in order for them to initialize at all. In the robot container(which is also an object), we usually do this:

init{
Arm
Intake
Drivetrain
}

Where the singleton names are Arm, Intake, etc.

If you don’t do this, the subsystems won’t be initialized in the way you need them to be.

One thing to clarify - if you’re doing command based, absolutely follow pattern to define your subsystems like the architecture asks, don’t try to sidestep it with singletons.

Outside of command based, I do like singletons for FRC for one specific reason: They help ease the confusion of new folks between “Arm, the class” and “Arm, the object” when they can clearly look at the robot and see one and only one arm.

Granted, this is a specific choice in pedagogy related toward speed of development, not seating good long-term habits. It takes some solid oversight to prevent blindly applying the pattern and creating footguns. Your team’s mileage might vary.

2 Likes

IMO robot subsystems are one of the places singletons make the most sense, since there can only be one Drivetrain, Arm, Intake, etc at a time on your robot.

The issue here is that the Singleton abstraction couples “there can only be one of these” with “this can be accessed from anywhere at any time.” The latter is a huge problem in practice.

There is already a guarantee that you can’t dual-allocate the actual hardware that a Subsystem encapsulates (hardware classes crash the robot program on a double allocation), so the “uniqueness” feature isn’t actually guarding you against bad program behavior unless you’re running completely untested code on the field.

This comes at the cost of allowing global access to the entire public Subsystem contract, which can easily cause any number of hard-to-diagnose robot behaviors even on code you’ve tested a whole bunch.

4 Likes

Could you give me an example of this? I don’t see a straightforward reason why using Singletons would cause errors to become harder to diagnose, although I’m willing to be proved wrong.

As a rule, software is easier to reason about and debug if you know what parts of the code are allowed to modify what parts of the application state. Any global application state has to be kept in mind by the programmer at all times, regardless of what local file they’re in. The more information the programmer has to think about to produce a correct program, the more bugs you’re going to see and the harder it’s going to be to fix them.

Singletons are globally-accessible - the state of a singleton can be directly modified by any other code that depends on the singleton. So, to debug an issue with inconsistent subsystem state, you have many more potential callsites to investigate if that subsystem is a singleton than if it is an ordinary local class instance.

To be concrete: if you’re in the habit of writing command classes that call subsystem methods through global access (as @asid61 described earlier), it’s quite easy for a confused developer to access a subsystem from a command that does not require that subsystem - and tracking down such mistakes once code visibly misbehaves can be quite a pain.

1 Like

I wouldn’t recommend using Singletons because they are harder to teach and understand (and less useful to teach than other stuff). Saying Call Arm.get() and it’ll magically give you the arm doesn’t really reinforce where that’s coming from and where the arm was created.

We actually just pass our entire robot object into the constructor of every subsystem and every command. I find that it helps reinforce the structure of the robot code as the students have to access everything through the root object.

the state of a singleton can be directly modified by any other code

I have prototyped out the idea of creating a RobotView thats a copy of the robot that dynamically and recursively blocks access to all variable writes and all setters (including vendor library setters), but found it might be a bit slow for using on the rio when the access checking is done at call time. It would probably be usable if the RobotView was generated at startup but thats harder to do.

This effectively makes the entire robot global, though, and won’t necessarily teach your students to think about what parts of the robot each individual piece of code needs to access. It’s more explicit but it has the same problem. What do you do if all those different dependencies of robot start wrestling over the internal state?

1 Like

The case you’re talking about is often negated by using the subsystem requirements for a command properly. And if you don’t do that, then you run the risk of double access anyway, singletons or not.

I understand where you’re coming from, but I think it mostly boils down to personal preference. I can think of few cases where this would be an issue where it otherwise would not be.

2 Likes

So we specify subsystem requirements properly for the scheduler to behave.

And we do have a rule that that we don’t modify other subsystems. Only read from them. It allows us to read from every sensor on the robot without DI hassle while also letting us organize sensors into subsystems where it makes sense instead of having a singleton sensorstore.

It’s also an alternative to a superstructure in cases where subsystems can physically collide.
So Subsystem A’s behavior can depend on B’s sensors. But A can’t modify B’s state directly.

Making a ReadOnlyRobot would enforce this is in code instead of by eye.

1 Like

True, but if you have to pass the subsystems to commands via constructor parameters, it makes it easier to see if you haven’t set up the requirements correctly. You have to assign it to a local variable in the constructor and ideally add the requirement right there. If you forget to pass a subsystem in, when you go to use it in execute, you’ll get a compile-time error.

If you pass nothing, you can call Drivetrain.getInstance() in execute and have no reason to even look at the constructor and notice that it’s missing that particular requirement.

That said, if it works for you…

1 Like

What if I want to access my elevator encoder in my drivetrain so that I can do tipping compensation? I probably don’t want to add an elevator requirement to my drivetrain, but I still need to pass in the elevator object if I’m not mistaken. So you still have cases where you need to set requirements independent of what’s passed in.