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.
While AppHooks were cool, the real draw of Sinister is the Filtering, the
AppHooks are built upon it after all!
What is Classpath Filtering?
Classpath filtering allows you to run some reflection code on every class in the
classpath. The SDK uses this to look for their own AppHooks, and is how they
look for your OpModes with the @Teleop, @Autonomous and @Disabled
annotations.
Unlike the SDK, Sinister is ‘bootstrapping’, which means it discovers
SinisterFilters through its own scanning process, rather than requiring direct
registration.
This means that libraries can easily declare their own SinisterFilters and
Sinister will automatically find and run them, as opposed to the SDK, where
filters can only be added by modifying the FtcRobotControllerActivity.java
file. This needs to be done manually for every repository that needs an
additional filter added, and while as of SDK version 9.X.X this is still
possible, however, FIRST have announced that they are no longer going to be
supporting this going forward.
Nice!
Ok, how do we do it?
Lets implement a SinisterFilter that:
- Looks for instances of classes that implement our custom
EventReceiver
interface with our own custom annotation.
- Stores the instances it finds, and notifies all
EventReceivers when the
user publishes an Event.
EventReceiver:
// This is from Sinister, this means that any class that implements this
// interface will be `preloaded` this means we can look for static instances in
// these classes
@Preload
@FunctionalInterface
public interface EventReceiver {
void receiveEvent(int eventLevel, String message);
}
Filter:
public final class JavaEventManager {
// we'll make it possible to publish an event from anywhere
public static void publishEvent(int eventLevel, String message) {
EventReceiverFilter.eventReceivers.forEach(javaEventReceiver ->
javaEventReceiver.receiveEvent(eventLevel, message)
);
}
// this will collect the instances of EventReceiver
private static final class EventReceiverFilter implements SinisterFilter {
private EventReceiverFilter() {}
public static final EventReceiverFilter INSTANCE = new EventReceiverFilter();
private static final ArrayList<JavaEventReceiver> eventReceivers = new ArrayList<>();
// we'll only look in the TeamCode module
private static final SearchTarget searchTarget = new TeamCodeSearch();
@NonNull
@Override
public SearchTarget getTargets() {
return searchTarget;
}
@Override
public void init() {
eventReceivers.clear();
}
@Override
public void filter(@NonNull Class<?> clazz) {
eventReceivers.addAll(SinisterUtil.staticInstancesOf(clazz, JavaEventReceiver.class));
}
}
}
These examples are also available in the TeamCode module in the Dairy mono repo.
TargetSearches
TargetSearches allow you to prefilter the packages that your filter will search.
You can easily create your own, they have standard include and exclude
methods that are super intuitive to use.
// includes nothing, add your own!
new EmptySearch();
// an EmptySearch that includes the TeamCode module
new TeamCodeSearch();
// includes everything, except some packages that cause issues, or are general
// systems packages (android, google, sun, etc.)
new FullSearch();
// generally should be preferred over FullSearch, also removes packages that
// the SDK utilities exclude
new WideSearch();
// a WideSearch that also excludes the SDK itself, this is good for TeamCode and
// libraries
new NarrowSearch();
// a NarrowSearch that also excludes Dairy libraries
new FocusedSearch();
Each of these can be subclassed and used as a base, with your own modifications:
// remember that this doesn't include anything to start
new EmptySearch()
// will now include everything in the packages dev.frozenmilk.**
.include("dev.frozenmilk")
// but now dev.frozenmilk.sinister.** won't be included
.exclude("dev.frozenmilk.sinister");
// so, classes "dev.frozenmilk.Demo", dev.frozenmilk.dairy.Demo", and
// dev.frozenmilk.dairy.Demo2" will be included
// but classes "dev.frozenmilk.sinister.Demo",
// "dev.frozenmilk.sinister.filtering.Demo" won't be
Easy!