Sloth
Lightning-fast hot code reload for FTC
What is Sloth?
Sloth is the fastest hot code reload library for FTC, developed by Dairy Foundation. It enables ultra-fast Write → Run → Test development cycles by uploading only your TeamCode to the robot in under 1 second, eliminating the typical 40+ second wait.
Sloth is also a Sinister runtime that enables classpath scanning and dynamic loading on Android FTC, supporting libraries that need to react to hot reloading.
Speed Comparison: Traditional deployment takes 40+ seconds. Sloth takes under 1 second.
Why Use Sloth?
Problems with Traditional FTC Development:
- Every code change requires full rebuild (40+ seconds)
- Slow feedback loop kills productivity
- Testing small changes takes forever
- Waiting disrupts flow state
Sloth Solutions:
- Upload code in under 1 second
- Changes persist across restarts and power cycles
- Safe deployment - processes changes after OpMode ends
- Works with FTC Dashboard, Panels, and other libraries
- Includes
@Pinnedannotation to prevent hot-swapping critical classes - Supports custom classpath scanning and hot reload listeners
Key Improvements Over Fastload
Sloth is the successor to fastload with major enhancements:
| Feature | fastload | Sloth |
|---|---|---|
| Upload Speed | ~7 seconds | ~1 second |
| Persistence | Lost on restart | Survives restarts |
| Deployment Safety | Immediate | After OpMode ends |
| Runtime Capabilities | Basic | Full Sinister runtime |
| Dashboard Support | Limited | Full hot reload support |
| SDK Updates | Manual | Automatic (including hardware map) |
How It Works
Sloth uses dynamic class loading to:
- Detect Changes: Monitors TeamCode for modifications
- Upload Fast: Sends only changed code (not entire app)
- Safe Swap: Waits for OpMode to end before applying changes
- Update SDK: Refreshes hardware map and other SDK components
- Notify Libraries: Triggers hot reload listeners in compatible libraries
Scope and Limitations
Hot reloaded:
- Classes in
org.firstinspires.ftc.teamcodepackage - Your OpModes and custom classes
- Drivers uploaded with your code
NOT hot reloaded:
- Classes outside TeamCode package
@Pinnedclasses (and their subclasses)- Library changes (requires full install)
- SDK modifications
Important: If you make changes that aren't hot reloaded (libraries, SDK, etc.), perform a full install to propagate them.
Installation
Option 1: Dairy Templates (Recommended)
Use Dairy Templates with the teamcode-sloth template:
ftc {
dairy {
implementation(Sloth)
// Optional: Sloth-compatible FTC Dashboard
implementation(slothboard)
// Optional: Sloth-compatible Panels
implementation(ftControl.fullpanels)
}
}
plugins {
id("dev.frozenmilk.teamcode") version "11.0.0-1.1.0"
id("dev.frozenmilk.sinister.sloth.load") version "0.2.4"
}Option 2: Manual Installation
Step 1: Add Repositories
Add to your TeamCode build.gradle:
repositories {
maven {
url = "https://repo.dairy.foundation/releases"
}
maven {
url = "https://repo.dairy.foundation/snapshots"
}
}repositories {
maven {
url = uri("https://repo.dairy.foundation/releases")
}
maven {
url = uri("https://repo.dairy.foundation/snapshots")
}
}Step 2: Install Sloth Library
Choose based on whether you use other Dairy libraries:
If NOT using other Dairy 1.x.x libraries:
dependencies {
implementation("dev.frozenmilk.sinister:Sloth:0.2.4")
}If using Mercurial, Core, or other Dairy libraries:
dependencies {
// This includes Sloth
implementation("dev.frozenmilk.dairy:Core:2.2.4")
}⚠️ Remove any existing Util or Sinister dependencies - Core provides them.
Step 3: Install Load Plugin
Add to top of TeamCode build.gradle:
buildscript {
repositories {
mavenCentral()
maven {
url "https://repo.dairy.foundation/releases"
}
}
dependencies {
classpath "dev.frozenmilk:Load:0.2.4"
}
}Add after the apply plugin: lines:
apply plugin: 'dev.frozenmilk.sinister.sloth.load'Step 4: Optional - FTC Dashboard
For Sloth-compatible FTC Dashboard:
dependencies {
implementation("com.acmerobotics.slothboard:dashboard:0.2.4+0.5.1")
}If using Road Runner, exclude standard dashboard:
implementation("com.acmerobotics.roadrunner:ftc:0.1.25") {
exclude group: "com.acmerobotics.dashboard"
}
implementation("com.acmerobotics.roadrunner:actions:1.0.1") {
exclude group: "com.acmerobotics.dashboard"
}Step 5: Optional - Panels
For Sloth-compatible Panels:
dependencies {
implementation("com.bylazar.sloth:fullpanels:0.2.4+1.0.12")
}Step 6: Setup Gradle Tasks
Configure Android Studio tasks for easy deployment.
Add deploySloth task:
- Edit Configurations
- Add new Configuration → Gradle
- Task:
deploySloth - Save
Add removeSlothRemote to TeamCode configuration:
- Edit TeamCode configuration
- Add Gradle task
- Task:
removeSlothRemote - Put it first in the task list
- Save
Tip: Type :TeamCode in the Gradle Project box to get the right context.
Usage
Development Workflow
- Initial Deploy: Run
deploySlothtask (deploys full app with Sloth) - Make Changes: Edit your TeamCode files
- Fast Deploy: Run
deploySlothagain (uploads only changes in ~1s) - Test: OpMode stops → Changes apply → Test immediately
- Repeat: Keep iterating rapidly
Example: Fast Iteration
@TeleOp(name = "Quick Test")
public class QuickTest extends LinearOpMode {
@Override
public void runOpMode() {
DcMotor motor = hardwareMap.get(DcMotor.class, "motor");
waitForStart();
while (opModeIsActive()) {
// Try different values quickly
double power = 0.5; // Change this
motor.setPower(power);
telemetry.addData("Power", power);
telemetry.update();
}
// Stop OpMode → Changes deployed automatically
}
}Workflow:
- Run OpMode with
power = 0.5 - Stop OpMode
- Change to
power = 0.7 - Hit
deploySloth(takes less than 1 second) - Run OpMode again - new value active!
@TeleOp(name = "Quick Test")
class QuickTest : LinearOpMode() {
override fun runOpMode() {
val motor = hardwareMap.get(DcMotor::class.java, "motor")
waitForStart()
while (opModeIsActive()) {
// Try different values quickly
val power = 0.5 // Change this
motor.power = power
telemetry.addData("Power", power)
telemetry.update()
}
// Stop OpMode → Changes deployed automatically
}
}Workflow:
- Run OpMode with
power = 0.5 - Stop OpMode
- Change to
power = 0.7 - Hit
deploySloth(takes under 1 second) - Run OpMode again - new value active!
The @Pinned Annotation
Use @Pinned to prevent hot reloading specific classes:
import dev.frozenmilk.sinister.targeting.Pinned;
// This class WON'T be hot reloaded
@Pinned
public class CriticalHardwareConfig {
// Hardware initialization that shouldn't change mid-session
public static final double WHEEL_DIAMETER = 4.0;
public static final double GEAR_RATIO = 1.5;
}
// Subclasses of @Pinned are also pinned
public class ExtendedConfig extends CriticalHardwareConfig {
// This also won't hot reload
}import dev.frozenmilk.sinister.targeting.Pinned
// This class WON'T be hot reloaded
@Pinned
class CriticalHardwareConfig {
companion object {
// Hardware initialization that shouldn't change mid-session
const val WHEEL_DIAMETER = 4.0
const val GEAR_RATIO = 1.5
}
}
// Subclasses of @Pinned are also pinned
class ExtendedConfig : CriticalHardwareConfig() {
// This also won't hot reload
}Use @Pinned for:
- Hardware configuration classes
- Critical constants
- Classes with native code
- Classes that break when hot reloaded
Real-World Use Cases
1. PID Tuning
@TeleOp(name = "PID Tuner")
public class PIDTuner extends LinearOpMode {
@Override
public void runOpMode() {
DcMotorEx motor = hardwareMap.get(DcMotorEx.class, "arm");
// Tune these quickly with Sloth
double kP = 0.01;
double kI = 0.0;
double kD = 0.0;
PIDController controller = new PIDController(kP, kI, kD);
waitForStart();
while (opModeIsActive()) {
double current = motor.getCurrentPosition();
double target = 1000;
double power = controller.calculate(current, target);
motor.setPower(power);
telemetry.addData("Position", current);
telemetry.addData("Target", target);
telemetry.addData("Power", power);
telemetry.update();
}
}
}
// Change kP, kI, kD values → deploySloth → test immediately!@TeleOp(name = "PID Tuner")
class PIDTuner : LinearOpMode() {
override fun runOpMode() {
val motor = hardwareMap.get(DcMotorEx::class.java, "arm")
// Tune these quickly with Sloth
val kP = 0.01
val kI = 0.0
val kD = 0.0
val controller = PIDController(kP, kI, kD)
waitForStart()
while (opModeIsActive()) {
val current = motor.currentPosition.toDouble()
val target = 1000.0
val power = controller.calculate(current, target)
motor.power = power
telemetry.addData("Position", current)
telemetry.addData("Target", target)
telemetry.addData("Power", power)
telemetry.update()
}
}
}
// Change kP, kI, kD values → deploySloth → test immediately!2. Debugging Motion Profiles
@Autonomous(name = "Motion Debug")
public class MotionDebug extends LinearOpMode {
@Override
public void runOpMode() {
// Quickly test different profiles
double maxVel = 50.0; // Change these
double maxAccel = 30.0;
double distance = 24.0;
waitForStart();
// Run motion with current values
runMotionProfile(maxVel, maxAccel, distance);
// Stop → Change values → deploySloth → Run again
}
}@Autonomous(name = "Motion Debug")
class MotionDebug : LinearOpMode() {
override fun runOpMode() {
// Quickly test different profiles
val maxVel = 50.0 // Change these
val maxAccel = 30.0
val distance = 24.0
waitForStart()
// Run motion with current values
runMotionProfile(maxVel, maxAccel, distance)
// Stop → Change values → deploySloth → Run again
}
}Precautions
What Can Break Hot Reloading
Installing/changing libraries - Requires full install
dependencies {
// If you add this:
implementation("com.newlibrary:lib:1.0.0")
// You MUST do full install, not deploySloth
}Changing @Pinned annotations - Needs full install
// If you add or remove @Pinned:
@Pinned // ← Added this
public class MyClass { }
// Full install required!Modifying files outside TeamCode - Not hot reloaded
src/main/java/
com/qualcomm/... ← SDK files (not hot reloaded)
org/firstinspires/ftc/teamcode/ ← Your code (hot reloaded)Pedro Pathing Warning
If you used an older Pedro Pathing quickstart, move your files to org.firstinspires.ftc.teamcode package or they won't be hot reloaded.
Integration with Libraries
FTC Dashboard (Slothboard)
Sloth includes a modified FTC Dashboard called "Slothboard":
- Full hot reload support for
@Configclasses - OpModes automatically refresh
- Configuration variables update without full install
Panels
Sloth-compatible Panels provides:
- Full plugin system with hot reload
- Real-time configurables
- Live telemetry updates
Custom Libraries
Make your library Sloth-compatible:
import dev.frozenmilk.sinister.SinisterFilter
// Listen for hot reloads
class MyLibraryModule : SinisterFilter {
override fun accept(clazz: Class<*>): Boolean {
// Return true for classes you care about
return clazz.isAnnotationPresent(MyAnnotation::class.java)
}
override fun init(clazz: Class<*>) {
// Called when Sloth loads/reloads this class
println("Reloaded: ${clazz.name}")
}
}import dev.frozenmilk.sinister.SinisterFilter;
// Listen for hot reloads
public class MyLibraryModule implements SinisterFilter {
@Override
public boolean accept(Class<?> clazz) {
// Return true for classes you care about
return clazz.isAnnotationPresent(MyAnnotation.class);
}
@Override
public void init(Class<?> clazz) {
// Called when Sloth loads/reloads this class
System.out.println("Reloaded: " + clazz.getName());
}
}Comparison: Sloth vs Traditional
| Aspect | Traditional | Sloth |
|---|---|---|
| First Deploy | 40+ seconds | 40+ seconds |
| Subsequent Deploys | 40+ seconds | <1 second |
| Changes Persist | Yes | Yes |
| Works Across Restarts | Yes | Yes |
| Library Changes | Full install | Full install |
| TeamCode Changes | Full install | Hot reload |
| OpMode Safety | Immediate | After OpMode ends |
| Dashboard/Panels | Standard | Hot reload support |
Tips for Success
- Initial Setup Takes Time: First
deploySlothis still slow (full install) - Subsequent Deploys Are Fast: Every deploy after that is under 1 second
- Stop OpMode First: Changes apply when OpMode ends
- Full Install When Needed: Library/SDK changes need full install
- Use @Pinned Wisely: Only pin truly critical classes
- Organize Code Well: Keep frequently-changed code together
- Test Iteratively: Make small changes and test frequently
- Watch for Errors: If something breaks, try full install
Troubleshooting
"Changes Aren't Applying"
- Did you run
deploySloth? - Did you stop the OpMode?
- Are files in
org.firstinspires.ftc.teamcodepackage? - Is the class
@Pinned?
"It's Still Slow"
- Check you're running
deploySlothnot full install - Ensure Load plugin is properly configured
- Verify Sloth dependency is correct
"Library Not Working"
- Some libraries need special Sloth versions (Dashboard, Panels)
- Check if library supports hot reloading
- Ask library maintainer about Sloth compatibility
When to Use Sloth
Use Sloth when:
- Actively developing and testing
- Tuning PIDs and constants
- Debugging autonomous routines
- Rapid prototyping
- Learning FTC programming
Don't need Sloth when:
- Code is stable (competition day)
- Only making library changes
- Working with small codebase (few changes)
- Team uncomfortable with advanced tools
Resources
- GitHub: Dairy-Foundation/Sloth
- Documentation: docs.dairy.foundation
- Templates: Dairy Templates
- Repository: repo.dairy.foundation
- Sinister Docs: Sinister Overview
Next Steps
- Choose installation method (Templates or Manual)
- Install Sloth with Load plugin
- Configure
deploySlothandremoveSlothRemotetasks - Run first
deploySloth(full install) - Make a small code change
- Run
deploySlothagain (should be less than 1 second) - Test the change immediately
- Enjoy lightning-fast iteration!
Pro Tip: Combine Sloth with FTC Dashboard or Panels for an unbeatable development experience with real-time tuning and visualization.