001// Copyright (c) FIRST and other WPILib contributors. 002// Open Source Software; you can modify and/or share it under the terms of 003// the WPILib BSD license file in the root directory of this project. 004 005package org.wpilib.framework; 006 007import java.io.File; 008import java.io.IOException; 009import java.lang.reflect.Constructor; 010import java.lang.reflect.Modifier; 011import java.net.URL; 012import java.util.Enumeration; 013import java.util.HashMap; 014import java.util.Map; 015import java.util.concurrent.atomic.AtomicReference; 016import java.util.function.Supplier; 017import java.util.jar.JarEntry; 018import java.util.jar.JarFile; 019import org.wpilib.driverstation.DriverStation; 020import org.wpilib.hardware.hal.ControlWord; 021import org.wpilib.hardware.hal.DriverStationJNI; 022import org.wpilib.hardware.hal.HAL; 023import org.wpilib.hardware.hal.NotifierJNI; 024import org.wpilib.hardware.hal.RobotMode; 025import org.wpilib.opmode.Autonomous; 026import org.wpilib.opmode.OpMode; 027import org.wpilib.opmode.Teleop; 028import org.wpilib.opmode.TestOpMode; 029import org.wpilib.util.Color; 030import org.wpilib.util.WPIUtilJNI; 031 032/** 033 * OpModeRobot implements the opmode-based robot program framework. 034 * 035 * <p>The OpModeRobot class is intended to be subclassed by a user creating a robot program. 036 * 037 * <p>Classes annotated with {@link Autonomous}, {@link Teleop}, and {@link TestOpMode} in the same 038 * package or subpackages as the user's subclass will be automatically registered as autonomous, 039 * teleop, and test opmodes respectively. 040 * 041 * <p>Opmodes are constructed when selected on the driver station, and closed/no longer used when 042 * the robot is disabled after being enabled or a different opmode is selected. When no opmode is 043 * selected, nonePeriodic() is called. The driverStationConnected() function is called the first 044 * time the driver station connects to the robot. 045 */ 046public abstract class OpModeRobot extends RobotBase { 047 private final ControlWord m_word = new ControlWord(); 048 049 private record OpModeFactory(String name, Supplier<OpMode> supplier) {} 050 051 private final Map<Long, OpModeFactory> m_opModes = new HashMap<>(); 052 private final AtomicReference<OpMode> m_activeOpMode = new AtomicReference<>(null); 053 private volatile int m_notifier; 054 055 private static void reportAddOpModeError(Class<?> cls, String message) { 056 DriverStation.reportError("Error adding OpMode " + cls.getSimpleName() + ": " + message, false); 057 } 058 059 /** 060 * Find a public constructor to instantiate the opmode. Prefer a single-arg public constructor 061 * whose parameter type is assignable from this.getClass() (if multiple, pick the most specific 062 * parameter type). Otherwise return the public no-arg constructor. Return null if neither exists. 063 */ 064 private Constructor<?> findOpModeConstructor(Class<?> cls) { 065 Constructor<?> bestCtor = null; 066 Class<?> bestParam = null; 067 for (Constructor<?> ctor : cls.getConstructors()) { 068 Class<?>[] params = ctor.getParameterTypes(); 069 if (params.length != 1) { 070 continue; 071 } 072 Class<?> param = params[0]; 073 if (!param.isAssignableFrom(getClass())) { 074 continue; 075 } 076 if (bestCtor == null || bestParam.isAssignableFrom(param)) { 077 bestCtor = ctor; 078 bestParam = param; 079 } 080 } 081 if (bestCtor != null) { 082 return bestCtor; 083 } 084 try { 085 return cls.getConstructor(); 086 } catch (NoSuchMethodException e) { 087 return null; 088 } 089 } 090 091 private OpMode constructOpModeClass(Class<?> cls) { 092 Constructor<?> constructor = findOpModeConstructor(cls); 093 if (constructor == null) { 094 DriverStation.reportError( 095 "No suitable constructor to instantiate OpMode " + cls.getSimpleName(), true); 096 return null; 097 } 098 try { 099 if (constructor.getParameterCount() == 1) { 100 return (OpMode) constructor.newInstance(this); 101 } else { 102 return (OpMode) constructor.newInstance(); 103 } 104 } catch (ReflectiveOperationException e) { 105 DriverStation.reportError( 106 "Could not instantiate OpMode " + cls.getSimpleName(), e.getStackTrace()); 107 return null; 108 } 109 } 110 111 private void checkOpModeClass(Class<?> cls) { 112 // the class must be a subclass of OpMode 113 if (!OpMode.class.isAssignableFrom(cls)) { 114 throw new IllegalArgumentException("not a subclass of OpMode"); 115 } 116 int modifiers = cls.getModifiers(); 117 // it cannot be abstract 118 if (Modifier.isAbstract(modifiers)) { 119 throw new IllegalArgumentException("is abstract"); 120 } 121 // it must be public 122 if (!Modifier.isPublic(modifiers)) { 123 throw new IllegalArgumentException("not public"); 124 } 125 // it must not be a non-static inner class 126 if (cls.getEnclosingClass() != null && !Modifier.isStatic(modifiers)) { 127 throw new IllegalArgumentException("is a non-static inner class"); 128 } 129 // it must have a public no-arg constructor or a public constructor that accepts this class 130 // (or a superclass/interface) as an argument 131 if (findOpModeConstructor(cls) == null) { 132 throw new IllegalArgumentException( 133 "missing public no-arg constructor or constructor accepting " 134 + getClass().getSimpleName()); 135 } 136 } 137 138 /** 139 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 140 * publishOpModes() to make the added mode visible to the driver station. 141 * 142 * @param factory factory function to create the opmode 143 * @param mode robot mode 144 * @param name name of the operating mode 145 * @param group group of the operating mode 146 * @param description description of the operating mode 147 * @param textColor text color, or null for default 148 * @param backgroundColor background color, or null for default 149 * @throws IllegalArgumentException if class does not meet criteria 150 */ 151 public void addOpModeFactory( 152 Supplier<OpMode> factory, 153 RobotMode mode, 154 String name, 155 String group, 156 String description, 157 Color textColor, 158 Color backgroundColor) { 159 long id = DriverStation.addOpMode(mode, name, group, description, textColor, backgroundColor); 160 m_opModes.put(id, new OpModeFactory(name, factory)); 161 } 162 163 /** 164 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 165 * publishOpModes() to make the added mode visible to the driver station. 166 * 167 * @param factory factory function to create the opmode 168 * @param mode robot mode 169 * @param name name of the operating mode 170 * @param group group of the operating mode 171 * @param description description of the operating mode 172 * @throws IllegalArgumentException if class does not meet criteria 173 */ 174 public void addOpModeFactory( 175 Supplier<OpMode> factory, RobotMode mode, String name, String group, String description) { 176 addOpModeFactory(factory, mode, name, group, description, null, null); 177 } 178 179 /** 180 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 181 * publishOpModes() to make the added mode visible to the driver station. 182 * 183 * @param factory factory function to create the opmode 184 * @param mode robot mode 185 * @param name name of the operating mode 186 * @param group group of the operating mode 187 * @throws IllegalArgumentException if class does not meet criteria 188 */ 189 public void addOpModeFactory( 190 Supplier<OpMode> factory, RobotMode mode, String name, String group) { 191 addOpModeFactory(factory, mode, name, group, ""); 192 } 193 194 /** 195 * Adds an opmode using a factory function that creates the opmode. It's necessary to call 196 * publishOpModes() to make the added mode visible to the driver station. 197 * 198 * @param factory factory function to create the opmode 199 * @param mode robot mode 200 * @param name name of the operating mode 201 * @throws IllegalArgumentException if class does not meet criteria 202 */ 203 public void addOpModeFactory(Supplier<OpMode> factory, RobotMode mode, String name) { 204 addOpModeFactory(factory, mode, name, ""); 205 } 206 207 /** 208 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 209 * with a public constructor that either takes no arguments or accepts a single argument of this 210 * class's type (the latter is preferred). It's necessary to call publishOpModes() to make the 211 * added mode visible to the driver station. 212 * 213 * @param cls class to add 214 * @param mode robot mode 215 * @param name name of the operating mode 216 * @param group group of the operating mode 217 * @param description description of the operating mode 218 * @param textColor text color, or null for default 219 * @param backgroundColor background color, or null for default 220 * @throws IllegalArgumentException if class does not meet criteria 221 */ 222 public void addOpMode( 223 Class<? extends OpMode> cls, 224 RobotMode mode, 225 String name, 226 String group, 227 String description, 228 Color textColor, 229 Color backgroundColor) { 230 checkOpModeClass(cls); 231 addOpModeFactory( 232 () -> constructOpModeClass(cls), 233 mode, 234 name, 235 group, 236 description, 237 textColor, 238 backgroundColor); 239 } 240 241 /** 242 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 243 * with a public constructor that either takes no arguments or accepts a single argument of this 244 * class's type (the latter is preferred). It's necessary to call publishOpModes() to make the 245 * added mode visible to the driver station. 246 * 247 * @param cls class to add 248 * @param mode robot mode 249 * @param name name of the operating mode 250 * @param group group of the operating mode 251 * @param description description of the operating mode 252 * @throws IllegalArgumentException if class does not meet criteria 253 */ 254 public void addOpMode( 255 Class<? extends OpMode> cls, RobotMode mode, String name, String group, String description) { 256 addOpMode(cls, mode, name, group, description, null, null); 257 } 258 259 /** 260 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 261 * with a public constructor that either takes no arguments or accepts a single argument of this 262 * class's type (the latter is preferred). It's necessary to call publishOpModes() to make the 263 * added mode visible to the driver station. 264 * 265 * @param cls class to add 266 * @param mode robot mode 267 * @param name name of the operating mode 268 * @param group group of the operating mode 269 * @throws IllegalArgumentException if class does not meet criteria 270 */ 271 public void addOpMode(Class<? extends OpMode> cls, RobotMode mode, String name, String group) { 272 addOpMode(cls, mode, name, group, ""); 273 } 274 275 /** 276 * Adds an opmode for an opmode class. The class must be a public, non-abstract subclass of OpMode 277 * with a public constructor that either takes no arguments or accepts a single argument of this 278 * class's type (the latter is preferred). It's necessary to call publishOpModes() to make the 279 * added mode visible to the driver station. 280 * 281 * @param cls class to add 282 * @param mode robot mode 283 * @param name name of the operating mode 284 * @throws IllegalArgumentException if class does not meet criteria 285 */ 286 public void addOpMode(Class<? extends OpMode> cls, RobotMode mode, String name) { 287 addOpMode(cls, mode, name, ""); 288 } 289 290 private void addOpModeClassImpl( 291 Class<?> cls, 292 RobotMode mode, 293 String name, 294 String group, 295 String description, 296 String textColor, 297 String backgroundColor) { 298 if (name == null || name.isBlank()) { 299 name = cls.getSimpleName(); 300 } 301 Color tColor = textColor.isBlank() ? null : Color.fromString(textColor); 302 Color bColor = backgroundColor.isBlank() ? null : Color.fromString(backgroundColor); 303 long id = DriverStation.addOpMode(mode, name, group, description, tColor, bColor); 304 m_opModes.put(id, new OpModeFactory(name, () -> constructOpModeClass(cls))); 305 } 306 307 private void addAnnotatedOpModeImpl( 308 Class<?> cls, Autonomous auto, Teleop teleop, TestOpMode test) { 309 checkOpModeClass(cls); 310 311 // add an opmode for each annotation 312 if (auto != null) { 313 addOpModeClassImpl( 314 cls, 315 RobotMode.AUTONOMOUS, 316 auto.name(), 317 auto.group(), 318 auto.description(), 319 auto.textColor(), 320 auto.backgroundColor()); 321 } 322 if (teleop != null) { 323 addOpModeClassImpl( 324 cls, 325 RobotMode.TELEOPERATED, 326 teleop.name(), 327 teleop.group(), 328 teleop.description(), 329 teleop.textColor(), 330 teleop.backgroundColor()); 331 } 332 if (test != null) { 333 addOpModeClassImpl( 334 cls, 335 RobotMode.TEST, 336 test.name(), 337 test.group(), 338 test.description(), 339 test.textColor(), 340 test.backgroundColor()); 341 } 342 } 343 344 /** 345 * Adds an opmode for an opmode class annotated with {@link Autonomous}, {@link Teleop}, or {@link 346 * TestOpMode}. The class must be a public, non-abstract subclass of OpMode with a public 347 * constructor that either takes no arguments or accepts a single argument of this class's type. 348 * It's necessary to call publishOpModes() to make the added mode visible to the driver station. 349 * 350 * @param cls class to add 351 * @throws IllegalArgumentException if class does not meet criteria 352 */ 353 public void addAnnotatedOpMode(Class<? extends OpMode> cls) { 354 Autonomous auto = cls.getAnnotation(Autonomous.class); 355 Teleop teleop = cls.getAnnotation(Teleop.class); 356 TestOpMode test = cls.getAnnotation(TestOpMode.class); 357 if (auto == null && teleop == null && test == null) { 358 throw new IllegalArgumentException( 359 "must be annotated with Autonomous, Teleop, or TestOpMode"); 360 } 361 addAnnotatedOpModeImpl(cls, auto, teleop, test); 362 } 363 364 private void addAnnotatedOpModeClass(String name) { 365 // trim ".class" from end 366 String className = name.replace('/', '.').substring(0, name.length() - 6); 367 Class<?> cls; 368 try { 369 cls = Class.forName(className); 370 } catch (ClassNotFoundException e) { 371 return; 372 } 373 Autonomous auto = cls.getAnnotation(Autonomous.class); 374 Teleop teleop = cls.getAnnotation(Teleop.class); 375 TestOpMode test = cls.getAnnotation(TestOpMode.class); 376 if (auto == null && teleop == null && test == null) { 377 return; 378 } 379 try { 380 addAnnotatedOpModeImpl(cls, auto, teleop, test); 381 } catch (IllegalArgumentException e) { 382 reportAddOpModeError(cls, e.getMessage()); 383 } 384 } 385 386 private void addAnnotatedOpModeClassesDir(File root, File dir, String packagePath) { 387 File[] files = dir.listFiles(); 388 if (files == null) { 389 return; 390 } 391 for (File file : files) { 392 if (file.isDirectory()) { 393 addAnnotatedOpModeClassesDir(root, file, packagePath); 394 } else if (file.getName().endsWith(".class")) { 395 String relPath = root.toPath().relativize(file.toPath()).toString().replace('\\', '/'); 396 addAnnotatedOpModeClass(packagePath + "." + relPath); 397 } 398 } 399 } 400 401 /** 402 * Scans for classes in the specified package and all nested packages that are annotated with 403 * {@link Autonomous}, {@link Teleop}, or {@link TestOpMode} and registers them. It's necessary to 404 * call publishOpModes() to make the added modes visible to the driver station. 405 * 406 * @param pkg package to scan 407 */ 408 public void addAnnotatedOpModeClasses(Package pkg) { 409 String packageName = pkg.getName(); 410 String packagePath = packageName.replace('.', '/'); 411 ClassLoader classLoader = Thread.currentThread().getContextClassLoader(); 412 413 try { 414 Enumeration<URL> resources = classLoader.getResources(packagePath); 415 while (resources.hasMoreElements()) { 416 URL resource = resources.nextElement(); 417 if ("jar".equals(resource.getProtocol())) { 418 // Get path of JAR file from URL path (format "file:<path_to_jar_file>!/path_to_entry") 419 String jarPath = resource.getPath().substring(5, resource.getPath().indexOf('!')); 420 try (JarFile jar = new JarFile(jarPath)) { 421 Enumeration<JarEntry> entries = jar.entries(); 422 while (entries.hasMoreElements()) { 423 String name = entries.nextElement().getName(); 424 if (!name.startsWith(packagePath) || !name.endsWith(".class")) { 425 continue; 426 } 427 addAnnotatedOpModeClass(name); 428 } 429 } 430 } else if ("file".equals(resource.getProtocol())) { 431 // Handle .class files in directories 432 File dir = new File(resource.getPath()); 433 if (dir.exists() && dir.isDirectory()) { 434 addAnnotatedOpModeClassesDir(dir, dir, packagePath); 435 } 436 } 437 } 438 } catch (IOException e) { 439 e.printStackTrace(); 440 } 441 } 442 443 /** 444 * Removes an operating mode option. It's necessary to call publishOpModes() to make the removed 445 * mode no longer visible to the driver station. 446 * 447 * @param mode robot mode 448 * @param name name of the operating mode 449 */ 450 public void removeOpMode(RobotMode mode, String name) { 451 long id = DriverStation.removeOpMode(mode, name); 452 if (id != 0) { 453 m_opModes.remove(id); 454 } 455 } 456 457 /** Publishes the operating mode options to the driver station. */ 458 public void publishOpModes() { 459 DriverStation.publishOpModes(); 460 } 461 462 /** Clears all operating mode options and publishes an empty list to the driver station. */ 463 public void clearOpModes() { 464 DriverStation.clearOpModes(); 465 m_opModes.clear(); 466 } 467 468 /** Constructor. */ 469 @SuppressWarnings("this-escape") 470 public OpModeRobot() { 471 // Scan for annotated opmode classes within the derived class's package and subpackages 472 addAnnotatedOpModeClasses(getClass().getPackage()); 473 DriverStation.publishOpModes(); 474 } 475 476 /** 477 * Function called exactly once after the DS is connected. 478 * 479 * <p>Code that needs to know the DS state should go here. 480 * 481 * <p>Users should override this method for initialization that needs to occur after the DS is 482 * connected, such as needing the alliance information. 483 */ 484 public void driverStationConnected() {} 485 486 /** 487 * Function called periodically anytime when no opmode is selected, including when the Driver 488 * Station is disconnected. 489 */ 490 public void nonePeriodic() {} 491 492 /** 493 * Background monitor thread. On mode/opmode change, this checks to see if the change is actually 494 * reflected in this class within a reasonable amount of time. If not, that means that the user 495 * code is stuck and we need to take action to try to get it to exit (up to and including program 496 * termination). 497 */ 498 private void monitorThreadMain(Thread thr, long opmode, int event, int endEvent) { 499 ControlWord word = new ControlWord(); 500 int[] events = {event, endEvent}; 501 while (true) { 502 try { 503 int[] signaled = WPIUtilJNI.waitForObjects(events); 504 for (int val : signaled) { 505 if (val < 0) { 506 return; // handle destroyed 507 } 508 } 509 } catch (InterruptedException e) { 510 Thread.currentThread().interrupt(); 511 return; 512 } 513 514 // did the opmode or enable state change? 515 DriverStationJNI.getUncachedControlWord(word); 516 if (!word.isEnabled() || word.getOpModeId() != opmode) { 517 break; 518 } 519 } 520 521 // call opmode stop 522 OpMode opMode = m_activeOpMode.get(); 523 if (opMode != null) { 524 opMode.opModeStop(); 525 } 526 527 events[0] = m_notifier; 528 NotifierJNI.setNotifierAlarm(m_notifier, 200000, 0, false, true); // 200 ms 529 try { 530 int[] signaled = WPIUtilJNI.waitForObjects(events); 531 for (int val : signaled) { 532 if (val < 0 || val == endEvent) { 533 return; // transitioned, or handle destroyed 534 } 535 } 536 } catch (InterruptedException e) { 537 Thread.currentThread().interrupt(); 538 return; 539 } 540 541 // if it hasn't transitioned after 200 ms, call thread.interrupt() 542 DriverStation.reportError("OpMode did not exit, interrupting thread", false); 543 thr.interrupt(); 544 545 NotifierJNI.setNotifierAlarm(m_notifier, 800000, 0, false, true); // 800 ms 546 try { 547 int[] signaled = WPIUtilJNI.waitForObjects(events); 548 for (int val : signaled) { 549 if (val < 0 || val == endEvent) { 550 return; // transitioned, or handle destroyed 551 } 552 } 553 } catch (InterruptedException e) { 554 Thread.currentThread().interrupt(); 555 return; 556 } 557 558 // if it hasn't transitioned after 1 second, terminate the program 559 DriverStation.reportError("OpMode did not exit, terminating program", false); 560 HAL.terminate(); 561 HAL.shutdown(); 562 System.exit(0); 563 } 564 565 /** Provide an alternate "main loop" via startCompetition(). */ 566 @Override 567 public final void startCompetition() { 568 System.out.println("********** Robot program startup complete **********"); 569 570 int event = WPIUtilJNI.createEvent(false, false); 571 DriverStationJNI.provideNewDataEventHandle(event); 572 573 m_notifier = NotifierJNI.createNotifier(); 574 NotifierJNI.setNotifierName(m_notifier, "OpModeRobot"); 575 576 try { 577 // Implement the opmode lifecycle 578 long lastModeId = -1; 579 boolean calledObserveUserProgramStarting = false; 580 boolean calledDriverStationConnected = false; 581 int[] events = {event, m_notifier}; 582 while (true) { 583 // Wait for new data from the driver station, with 50 ms timeout 584 NotifierJNI.setNotifierAlarm(m_notifier, 50000, 0, false, true); 585 586 // Call observeUserProgramStarting() here as a one-shot to ensure it is called after the 587 // notifier alarm is set. The notifier alarm is set using relative time, so tests that 588 // wait on the user program to start and then step time won't work correctly if we call 589 // this before setting the alarm. 590 if (!calledObserveUserProgramStarting) { 591 calledObserveUserProgramStarting = true; 592 DriverStationJNI.observeUserProgramStarting(); 593 } 594 595 try { 596 int[] signaled = WPIUtilJNI.waitForObjects(events); 597 for (int val : signaled) { 598 if (val < 0) { 599 return; // handle destroyed 600 } 601 } 602 } catch (InterruptedException ex) { 603 Thread.currentThread().interrupt(); 604 break; 605 } 606 607 // Get the latest control word and opmode 608 DriverStation.refreshData(); 609 DriverStation.refreshControlWordFromCache(m_word); 610 611 if (!calledDriverStationConnected && m_word.isDSAttached()) { 612 calledDriverStationConnected = true; 613 driverStationConnected(); 614 } 615 616 long modeId; 617 if (!m_word.isDSAttached()) { 618 modeId = 0; 619 } else { 620 modeId = m_word.getOpModeId(); 621 } 622 623 OpMode opMode = m_activeOpMode.get(); 624 if (opMode == null || modeId != lastModeId) { 625 if (opMode != null) { 626 // no or different opmode selected 627 m_activeOpMode.set(null); 628 opMode.opModeClose(); 629 } 630 631 if (modeId == 0) { 632 // no opmode selected 633 nonePeriodic(); 634 DriverStationJNI.observeUserProgram(m_word.getNative()); 635 continue; 636 } 637 638 OpModeFactory factory = m_opModes.get(modeId); 639 if (factory == null) { 640 DriverStation.reportError("No OpMode found for mode " + modeId, false); 641 m_word.setOpModeId(0); 642 DriverStationJNI.observeUserProgram(m_word.getNative()); 643 continue; 644 } 645 646 // Instantiate the opmode 647 System.out.println("********** Starting OpMode " + factory.name() + " **********"); 648 opMode = factory.supplier().get(); 649 if (opMode == null) { 650 // could not construct 651 m_word.setOpModeId(0); 652 DriverStationJNI.observeUserProgram(m_word.getNative()); 653 continue; 654 } 655 m_activeOpMode.set(opMode); 656 lastModeId = modeId; 657 // Ensure disabledPeriodic is always called at least once 658 opMode.disabledPeriodic(); 659 } 660 661 DriverStationJNI.observeUserProgram(m_word.getNative()); 662 663 if (m_word.isEnabled()) { 664 // When enabled, call the opmode run function, then close and clear 665 int endMonitor = WPIUtilJNI.createEvent(true, false); 666 Thread curThread = Thread.currentThread(); 667 Thread monitor = 668 new Thread( 669 () -> { 670 monitorThreadMain(curThread, modeId, event, endMonitor); 671 }); 672 monitor.start(); 673 try { 674 opMode.opModeRun(modeId); 675 } catch (InterruptedException e) { 676 // ignored 677 } finally { 678 Thread.interrupted(); 679 WPIUtilJNI.destroyEvent(endMonitor); 680 try { 681 monitor.join(); 682 } catch (InterruptedException e) { 683 Thread.currentThread().interrupt(); 684 } 685 } 686 opMode = m_activeOpMode.getAndSet(null); 687 if (opMode != null) { 688 opMode.opModeClose(); 689 } 690 } else { 691 // When disabled, call the disabledPeriodic function 692 opMode.disabledPeriodic(); 693 } 694 } 695 } finally { 696 DriverStationJNI.removeNewDataEventHandle(event); 697 WPIUtilJNI.destroyEvent(event); 698 NotifierJNI.destroyNotifier(m_notifier); 699 } 700 } 701 702 /** Ends the main loop in startCompetition(). */ 703 @Override 704 public final void endCompetition() { 705 NotifierJNI.destroyNotifier(m_notifier); 706 OpMode opMode = m_activeOpMode.get(); 707 if (opMode != null) { 708 opMode.opModeStop(); 709 } 710 } 711}