Skip to content

Schema registry

The registry maps a class to the schema used for it, everywhere.

Automatic registration

The first time you log a class, FateWeaver looks in the registry. On a miss, it builds a schema by reflection and stores it. That schema is a TypedClassSchema if the class has an AS_TYPE constant and a ReflectedClassSchema otherwise. Every later log of that class reuses the stored schema, so you pay the reflection cost once.

Manual registration

Register your own schema to replace the reflected default. After that, plain log(name, value) picks it up.

// Define once
val optimizedPoseSchema = CustomStructSchema<Pose2d>(
    type = "Pose2d",
    componentNames = listOf("x", "y", "theta"),
    componentSchemas = listOf(DoubleSchema.INSTANCE, DoubleSchema.INSTANCE, DoubleSchema.INSTANCE),
    encoder = { pose -> listOf(pose.position.x, pose.position.y, pose.heading.toDouble()) }
)

// Register globally
FateSchema.registerSchema(Pose2d::class.java, optimizedPoseSchema)

// Both paths use optimizedPoseSchema
Fate.log("Robot/Pose", robotPose)
Fate.log("Target/Pose", targetPose)
// Define once
CustomStructSchema<Pose2d> optimizedPoseSchema = new CustomStructSchema<>(
    "Pose2d",
    Arrays.asList("x", "y", "theta"),
    Arrays.asList(DoubleSchema.INSTANCE, DoubleSchema.INSTANCE, DoubleSchema.INSTANCE),
    pose -> Arrays.asList(pose.position.x, pose.position.y, pose.heading.toDouble())
);

// Register globally
FateSchema.registerSchema(Pose2d.class, optimizedPoseSchema);

// Both paths use optimizedPoseSchema
Fate.log("Robot/Pose", robotPose);
Fate.log("Target/Pose", targetPose);

A registered schema also applies to the class when it is a field of another class or an element of a list or map. It wins for every parameterization of the class.

Where to register

Register in your OpMode's init(), before anything logs that class. A path keeps the schema it had on its first log. If you register later, only new paths use it.

Keep every registration in one place. Registering the same class twice overwrites the first registration without a warning, and that is easier to spot when the calls sit next to each other.

abstract class BaseOpMode : OpMode() {
    override fun init() {
        setupSchemas()
        initHardware()
    }

    private fun setupSchemas() {
        // Register all custom schemas here
        FateSchema.registerSchema(Pose2d::class.java, OPTIMIZED_POSE_SCHEMA)
        FateSchema.registerSchema(PoseVelocity2d::class.java, VELOCITY_SCHEMA)
        FateSchema.registerSchema(RobotState::class.java, STATE_SCHEMA)
    }

    companion object {
        private val OPTIMIZED_POSE_SCHEMA = CustomStructSchema<Pose2d>(/* ... */)
        private val VELOCITY_SCHEMA = CustomStructSchema<PoseVelocity2d>(/* ... */)
        private val STATE_SCHEMA = CustomStructSchema<RobotState>(/* ... */)
    }
}
public abstract class BaseOpMode extends OpMode {
    @Override
    public final void init() {
        setupSchemas();
        initHardware();
    }

    private void setupSchemas() {
        // Register all custom schemas here
        FateSchema.registerSchema(Pose2d.class, OPTIMIZED_POSE_SCHEMA);
        FateSchema.registerSchema(PoseVelocity2d.class, VELOCITY_SCHEMA);
        FateSchema.registerSchema(RobotState.class, STATE_SCHEMA);
    }

    protected abstract void initHardware();

    private static final CustomStructSchema<Pose2d> OPTIMIZED_POSE_SCHEMA =
        new CustomStructSchema<>(/* ... */);
    private static final CustomStructSchema<PoseVelocity2d> VELOCITY_SCHEMA =
        new CustomStructSchema<>(/* ... */);
    private static final CustomStructSchema<RobotState> STATE_SCHEMA =
        new CustomStructSchema<>(/* ... */);
}