> ## Documentation Index
> Fetch the complete documentation index at: https://docs.dairy.foundation/llms.txt
> Use this file to discover all available pages before exploring further.

# OpMode Templating

OpMode templating the usage of inheritance and super classes to reuse things
like annotations and `OpModeLazyCell`s in OpModes.

OpMode templating is an alternative to writing a `RobotHardware` class / file,
that scales better through your usage of Dairy than using one would, because it
supports the reuse of Features that are enabled via annotation.

# What is a `RobotHardware` file?

<Note>
  I can no-longer find the article that I once read on gm0, as such, it is
  not linked here.
</Note>

I first came across this concept while reading a game manual 0 article during my
first season of programming for FTC. While by the time I had finished with the
season I was writing the previous version of the Mercurial library that some
might be familiar with, and had moved far beyond this concept, I still think it
was a useful construct to introduce me to the basic ideas of writing reusable
FTC code. Additionally, I continue to see teams employing the idea either as a
`RobotHardware` or `Robot` class, even when they have more powerful and flexible
tools already in use in their code! and it lead me down a path of trying (and
failing) several different ways to write reusable robot code in FTC.

The `RobotHardware` class serves the purpose of collecting all of your common
robot setup code into one place, which is then reused across all OpModes.

Advantages:

* Much better than duplicating code across OpModes.
* Allows for complex reusable methods and functions to allow for common complex
  control to be reused as well.
* With some work, allows for automatic management of systems that require
  frequent updates, like control loops.

Disadvantages:

* Inflexible, you can't pick and choose parts you want.
* Requires some boiler plate for each OpMode, but this could be avoided using
  Dairy `Feature`s.
* Gets bloated with needing to handle a whole bunch of logic for a whole bunch
  of systems.

In general, I advise for using subsystems, whether through a command based
library like FTClib or Dairy's Mercurial, or by writing them yourself (Mercurial
just thinly wraps over Dairy `Feature`s, and adds some tools for using them with
Mercurial, so its easy to create your own).

However, there is a great deal of merit in the reason that teams using
subsystems were still using one. They didn't want to repeat that boilerplate of
setting up their subsystems! So lets take a look at OpMode templating, an
alternate approach that supports reducing the boilerplate of Dairy annotations,
and supports the reducing the boilerplate of hardware.

A template:

<CodeGroup>
  ```java Java theme={null}
  // lets add that BulkRead feature from Writing + Using Features
  @BulkReads.Attach
  public abstract class Template extends OpMode {
  	// lets add OpModeLazyCells for each hardware item
  	private final OpModeLazyCell<DcMotorEx> leftFront
  			= new OpModeLazyCell<>(() -> hardwareMap.get(DcMotorEx.class, "lf"));
  	private final OpModeLazyCell<DcMotorEx> leftBack
  			= new OpModeLazyCell<>(() -> hardwareMap.get(DcMotorEx.class, "lb"));
  	// right motors need some additional setup
  	private final OpModeLazyCell<DcMotorEx> rightBack = new OpModeLazyCell<>(() -> {
  		DcMotorEx m = hardwareMap.get(DcMotorEx.class, "rb");
  		m.setDirection(DcMotorSimple.Direction.REVERSE);
  		return m;
  	});
  	private final OpModeLazyCell<DcMotorEx> rightFront = new OpModeLazyCell<>(() -> {
  		DcMotorEx m = hardwareMap.get(DcMotorEx.class, "rf");
  		m.setDirection(DcMotorSimple.Direction.REVERSE);
  		return m;
  	});

  	// and lets add getters to access the cells
  	public DcMotorEx getLeftFront() { return leftFront.get(); }
  	public DcMotorEx getLeftBack() { return leftBack.get(); }
  	public DcMotorEx getRightBack() { return rightBack.get(); }
  	public DcMotorEx getRightFront() { return rightFront.get(); }
  }
  ```

  ```kt Kotlin theme={null}
  // lets add that BulkRead feature from Writing + Using Features
  @BulkReads.Attach
  abstract class Template : OpMode() {
  	// lets add OpModeLazyCells for each hardware item
  	val leftFront: DcMotorEx by OpModeLazyCell {
  		hardwareMap.get(
  			DcMotorEx::class.java, "lf"
  		)
  	}
  	val leftBack: DcMotorEx by OpModeLazyCell {
  		hardwareMap.get(
  			DcMotorEx::class.java, "lb"
  		)
  	}

  	// right motors need some additional setup
  	val rightBack: DcMotorEx by OpModeLazyCell {
  		val m = hardwareMap.get(DcMotorEx::class.java, "rb")
  		m.direction = DcMotorSimple.Direction.REVERSE
  		m
  	}
  	val rightFront: DcMotorEx by OpModeLazyCell {
  		val m = hardwareMap.get(DcMotorEx::class.java, "rf")
  		m.direction = DcMotorSimple.Direction.REVERSE
  		m
  	}
  }
  ```
</CodeGroup>

Using it:

<CodeGroup>
  ```java Java theme={null}
  // we don't need to add the @BulkReads.Attach annotation
  public class Usage extends Template {
  	@Override
  	public void init() {
  	}
  	@Override
  	public void loop() {
  		// and we can access the methods from the Template
  		// thanks to OpModeLazyCell, the motors will be initialised in init
  		// rather than impact our first loop later
  		getLeftFront().setPower(-gamepad1.left_stick_y);
  		getLeftBack().setPower(-gamepad1.left_stick_y);
  		getRightBack().setPower(-gamepad1.left_stick_y);
  		getRightFront().setPower(-gamepad1.left_stick_y);
  	}
  }
  ```

  ```kt Kotlin theme={null}
  // we don't need to add the @BulkReads.Attach annotation
  class Usage : Template() {
  	override fun init() {
  	}

  	override fun loop() {
  		// and we can access the methods from the Template
  		// thanks to OpModeLazyCell, the motors will be initialised in init
  		// rather than impact our first loop later
  		leftFront.power = -gamepad1.left_stick_y.toDouble()
  		leftBack.power = -gamepad1.left_stick_y.toDouble()
  		rightBack.power = -gamepad1.right_stick_y.toDouble()
  		rightFront.power = -gamepad1.right_stick_y.toDouble()
  	}
  }
  ```
</CodeGroup>

We saved ourselves needing to re-write that annotation, or setup that hardware
again.

Take a look at the examples for a more advanced version of the same thing.
