From 0e7e3a69bd56c278f96587579aeca63dd570523c Mon Sep 17 00:00:00 2001 From: SMEZ1234 Date: Sat, 4 Jan 2020 21:05:19 +1100 Subject: [PATCH] Release 3.0-alpha Initial release --- .gitignore | 163 ++++++++++ .../arachne/examples/ArcadeDriveRobot.java | 30 ++ src/main/java/arachne/lib/ArachneRobot.java | 127 ++++++++ src/main/java/arachne/lib/BindingManager.java | 17 + .../java/arachne/lib/HardwareManager.java | 15 + .../arachne/lib/function/BooleanConsumer.java | 16 + .../arachne/lib/function/BooleanFunction.java | 13 + .../lib/function/BooleanPredicate.java | 69 ++++ src/main/java/arachne/lib/game/GameState.java | 17 + .../arachne/lib/hardware/ButtonPanel.java | 18 + .../lib/hardware/DifferentialDrivetrain.java | 76 +++++ .../SimpleDifferentialDrivetrain.java | 56 ++++ .../adaptors/BlinkinStateAdaptor.java | 59 ++++ .../DoubleSolenoidBooleanAdaptor.java | 24 ++ .../arachne/lib/immutables/DoublePair.java | 19 ++ .../lib/immutables/FlaggedBooleanValue.java | 20 ++ .../lib/immutables/FlaggedDoubleValue.java | 20 ++ .../arachne/lib/immutables/FlaggedValue.java | 20 ++ .../java/arachne/lib/immutables/Pair.java | 20 ++ .../java/arachne/lib/immutables/Triple.java | 26 ++ src/main/java/arachne/lib/io/Gettable.java | 21 ++ .../java/arachne/lib/io/GettableBoolean.java | 51 +++ .../java/arachne/lib/io/GettableDouble.java | 52 +++ src/main/java/arachne/lib/io/Settable.java | 9 + .../java/arachne/lib/io/SettableBoolean.java | 9 + .../java/arachne/lib/io/SettableDouble.java | 14 + .../lib/listeners/BooleanChangeHandler.java | 7 + .../arachne/lib/listeners/BooleanHook.java | 8 + .../lib/listeners/BooleanProperty.java | 14 + .../arachne/lib/listeners/ChangeHandler.java | 7 + .../lib/listeners/DoubleChangeHandler.java | 7 + .../arachne/lib/listeners/DoubleHook.java | 8 + .../arachne/lib/listeners/DoubleProperty.java | 56 ++++ src/main/java/arachne/lib/listeners/Hook.java | 8 + .../java/arachne/lib/listeners/Property.java | 14 + .../listeners/ReadOnlyBooleanProperty.java | 34 ++ .../lib/listeners/ReadOnlyDoubleProperty.java | 34 ++ .../lib/listeners/ReadOnlyProperty.java | 34 ++ .../arachne/lib/listeners/ReadOnlySignal.java | 25 ++ .../java/arachne/lib/listeners/Signal.java | 8 + .../lib/listeners/SimpleBooleanProperty.java | 25 ++ .../lib/listeners/SimpleDoubleProperty.java | 25 ++ .../arachne/lib/listeners/SimpleProperty.java | 25 ++ .../SimpleReadOnlyBooleanProperty.java | 20 ++ .../SimpleReadOnlyDoubleProperty.java | 20 ++ .../lib/listeners/SimpleReadOnlyProperty.java | 20 ++ .../arachne/lib/logging/ArachneLogger.java | 39 +++ .../java/arachne/lib/logic/ArachneMath.java | 13 + .../arachne/lib/logic/BooleanOperation.java | 102 ++++++ .../arachne/lib/logic/DoubleComparison.java | 54 +++ .../java/arachne/lib/logic/LinearMap.java | 66 ++++ .../lib/pipeline/AbstractBooleanSource.java | 40 +++ .../lib/pipeline/AbstractDoubleSource.java | 40 +++ .../arachne/lib/pipeline/AbstractSource.java | 40 +++ .../arachne/lib/pipeline/BooleanPipe.java | 22 ++ .../arachne/lib/pipeline/BooleanSource.java | 17 + .../lib/pipeline/BooleanTranslator.java | 25 ++ .../java/arachne/lib/pipeline/DoublePipe.java | 24 ++ .../arachne/lib/pipeline/DoubleSource.java | 17 + .../lib/pipeline/DoubleTranslator.java | 26 ++ src/main/java/arachne/lib/pipeline/Pipe.java | 24 ++ .../lib/pipeline/SimpleBooleanPipe.java | 81 +++++ .../lib/pipeline/SimpleBooleanSource.java | 18 + .../lib/pipeline/SimpleDoublePipe.java | 124 +++++++ .../lib/pipeline/SimpleDoubleSource.java | 18 + .../java/arachne/lib/pipeline/SimplePipe.java | 81 +++++ .../arachne/lib/pipeline/SimpleSource.java | 18 + .../java/arachne/lib/pipeline/Source.java | 20 ++ .../java/arachne/lib/pipeline/Translator.java | 26 ++ .../filters/ChangedBooleanFilter.java | 24 ++ .../pipeline/filters/ChangedDoubleFilter.java | 25 ++ .../lib/pipeline/filters/ChangedFilter.java | 25 ++ .../AbstractRequirementManager.java | 25 ++ .../lib/requirements/RequirementManager.java | 16 + .../arachne/lib/scheduler/Schedulable.java | 19 ++ .../lib/scheduler/ScheduledBooleanSource.java | 20 ++ .../lib/scheduler/ScheduledDoubleSource.java | 21 ++ .../lib/scheduler/ScheduledSource.java | 21 ++ .../java/arachne/lib/sequences/Action.java | 94 ++++++ .../lib/sequences/ActionConductor.java | 49 +++ .../arachne/lib/sequences/ActionWrapper.java | 44 +++ .../arachne/lib/sequences/Actionable.java | 7 + .../arachne/lib/sequences/BinarySelector.java | 68 ++++ .../java/arachne/lib/sequences/Binding.java | 32 ++ .../arachne/lib/sequences/BindingAction.java | 45 +++ .../arachne/lib/sequences/BooleanBinding.java | 31 ++ .../lib/sequences/BooleanBindingAction.java | 45 +++ .../java/arachne/lib/sequences/Clock.java | 22 ++ .../arachne/lib/sequences/DoubleBinding.java | 31 ++ .../lib/sequences/DoubleBindingAction.java | 45 +++ src/main/java/arachne/lib/sequences/Fork.java | 59 ++++ .../arachne/lib/sequences/HostAction.java | 23 ++ .../lib/sequences/RepeatableCondition.java | 40 +++ .../java/arachne/lib/sequences/Sequence.java | 30 ++ .../arachne/lib/sequences/SimpleSelector.java | 33 ++ .../lib/sequences/WithRequirement.java | 42 +++ src/main/java/arachne/lib/states/State.java | 16 + .../arachne/lib/states/StatefulSubsystem.java | 77 +++++ .../java/arachne/lib/systems/Subsystem.java | 131 ++++++++ src/main/java/arachne/lib/systems/Tree.java | 96 ++++++ .../arachne/tapestry/SequencesTapestry.java | 307 ++++++++++++++++++ .../arachne/test/io/GettableBooleanTest.java | 52 +++ .../arachne/test/logic/LinearMapTest.java | 104 ++++++ 103 files changed, 4034 insertions(+) create mode 100644 .gitignore create mode 100644 examples/arachne/examples/ArcadeDriveRobot.java create mode 100644 src/main/java/arachne/lib/ArachneRobot.java create mode 100644 src/main/java/arachne/lib/BindingManager.java create mode 100644 src/main/java/arachne/lib/HardwareManager.java create mode 100644 src/main/java/arachne/lib/function/BooleanConsumer.java create mode 100644 src/main/java/arachne/lib/function/BooleanFunction.java create mode 100644 src/main/java/arachne/lib/function/BooleanPredicate.java create mode 100644 src/main/java/arachne/lib/game/GameState.java create mode 100644 src/main/java/arachne/lib/hardware/ButtonPanel.java create mode 100644 src/main/java/arachne/lib/hardware/DifferentialDrivetrain.java create mode 100644 src/main/java/arachne/lib/hardware/SimpleDifferentialDrivetrain.java create mode 100644 src/main/java/arachne/lib/hardware/adaptors/BlinkinStateAdaptor.java create mode 100644 src/main/java/arachne/lib/hardware/adaptors/DoubleSolenoidBooleanAdaptor.java create mode 100644 src/main/java/arachne/lib/immutables/DoublePair.java create mode 100644 src/main/java/arachne/lib/immutables/FlaggedBooleanValue.java create mode 100644 src/main/java/arachne/lib/immutables/FlaggedDoubleValue.java create mode 100644 src/main/java/arachne/lib/immutables/FlaggedValue.java create mode 100644 src/main/java/arachne/lib/immutables/Pair.java create mode 100644 src/main/java/arachne/lib/immutables/Triple.java create mode 100644 src/main/java/arachne/lib/io/Gettable.java create mode 100644 src/main/java/arachne/lib/io/GettableBoolean.java create mode 100644 src/main/java/arachne/lib/io/GettableDouble.java create mode 100644 src/main/java/arachne/lib/io/Settable.java create mode 100644 src/main/java/arachne/lib/io/SettableBoolean.java create mode 100644 src/main/java/arachne/lib/io/SettableDouble.java create mode 100644 src/main/java/arachne/lib/listeners/BooleanChangeHandler.java create mode 100644 src/main/java/arachne/lib/listeners/BooleanHook.java create mode 100644 src/main/java/arachne/lib/listeners/BooleanProperty.java create mode 100644 src/main/java/arachne/lib/listeners/ChangeHandler.java create mode 100644 src/main/java/arachne/lib/listeners/DoubleChangeHandler.java create mode 100644 src/main/java/arachne/lib/listeners/DoubleHook.java create mode 100644 src/main/java/arachne/lib/listeners/DoubleProperty.java create mode 100644 src/main/java/arachne/lib/listeners/Hook.java create mode 100644 src/main/java/arachne/lib/listeners/Property.java create mode 100644 src/main/java/arachne/lib/listeners/ReadOnlyBooleanProperty.java create mode 100644 src/main/java/arachne/lib/listeners/ReadOnlyDoubleProperty.java create mode 100644 src/main/java/arachne/lib/listeners/ReadOnlyProperty.java create mode 100644 src/main/java/arachne/lib/listeners/ReadOnlySignal.java create mode 100644 src/main/java/arachne/lib/listeners/Signal.java create mode 100644 src/main/java/arachne/lib/listeners/SimpleBooleanProperty.java create mode 100644 src/main/java/arachne/lib/listeners/SimpleDoubleProperty.java create mode 100644 src/main/java/arachne/lib/listeners/SimpleProperty.java create mode 100644 src/main/java/arachne/lib/listeners/SimpleReadOnlyBooleanProperty.java create mode 100644 src/main/java/arachne/lib/listeners/SimpleReadOnlyDoubleProperty.java create mode 100644 src/main/java/arachne/lib/listeners/SimpleReadOnlyProperty.java create mode 100644 src/main/java/arachne/lib/logging/ArachneLogger.java create mode 100644 src/main/java/arachne/lib/logic/ArachneMath.java create mode 100644 src/main/java/arachne/lib/logic/BooleanOperation.java create mode 100644 src/main/java/arachne/lib/logic/DoubleComparison.java create mode 100644 src/main/java/arachne/lib/logic/LinearMap.java create mode 100644 src/main/java/arachne/lib/pipeline/AbstractBooleanSource.java create mode 100644 src/main/java/arachne/lib/pipeline/AbstractDoubleSource.java create mode 100644 src/main/java/arachne/lib/pipeline/AbstractSource.java create mode 100644 src/main/java/arachne/lib/pipeline/BooleanPipe.java create mode 100644 src/main/java/arachne/lib/pipeline/BooleanSource.java create mode 100644 src/main/java/arachne/lib/pipeline/BooleanTranslator.java create mode 100644 src/main/java/arachne/lib/pipeline/DoublePipe.java create mode 100644 src/main/java/arachne/lib/pipeline/DoubleSource.java create mode 100644 src/main/java/arachne/lib/pipeline/DoubleTranslator.java create mode 100644 src/main/java/arachne/lib/pipeline/Pipe.java create mode 100644 src/main/java/arachne/lib/pipeline/SimpleBooleanPipe.java create mode 100644 src/main/java/arachne/lib/pipeline/SimpleBooleanSource.java create mode 100644 src/main/java/arachne/lib/pipeline/SimpleDoublePipe.java create mode 100644 src/main/java/arachne/lib/pipeline/SimpleDoubleSource.java create mode 100644 src/main/java/arachne/lib/pipeline/SimplePipe.java create mode 100644 src/main/java/arachne/lib/pipeline/SimpleSource.java create mode 100644 src/main/java/arachne/lib/pipeline/Source.java create mode 100644 src/main/java/arachne/lib/pipeline/Translator.java create mode 100644 src/main/java/arachne/lib/pipeline/filters/ChangedBooleanFilter.java create mode 100644 src/main/java/arachne/lib/pipeline/filters/ChangedDoubleFilter.java create mode 100644 src/main/java/arachne/lib/pipeline/filters/ChangedFilter.java create mode 100644 src/main/java/arachne/lib/requirements/AbstractRequirementManager.java create mode 100644 src/main/java/arachne/lib/requirements/RequirementManager.java create mode 100644 src/main/java/arachne/lib/scheduler/Schedulable.java create mode 100644 src/main/java/arachne/lib/scheduler/ScheduledBooleanSource.java create mode 100644 src/main/java/arachne/lib/scheduler/ScheduledDoubleSource.java create mode 100644 src/main/java/arachne/lib/scheduler/ScheduledSource.java create mode 100644 src/main/java/arachne/lib/sequences/Action.java create mode 100644 src/main/java/arachne/lib/sequences/ActionConductor.java create mode 100644 src/main/java/arachne/lib/sequences/ActionWrapper.java create mode 100644 src/main/java/arachne/lib/sequences/Actionable.java create mode 100644 src/main/java/arachne/lib/sequences/BinarySelector.java create mode 100644 src/main/java/arachne/lib/sequences/Binding.java create mode 100644 src/main/java/arachne/lib/sequences/BindingAction.java create mode 100644 src/main/java/arachne/lib/sequences/BooleanBinding.java create mode 100644 src/main/java/arachne/lib/sequences/BooleanBindingAction.java create mode 100644 src/main/java/arachne/lib/sequences/Clock.java create mode 100644 src/main/java/arachne/lib/sequences/DoubleBinding.java create mode 100644 src/main/java/arachne/lib/sequences/DoubleBindingAction.java create mode 100644 src/main/java/arachne/lib/sequences/Fork.java create mode 100644 src/main/java/arachne/lib/sequences/HostAction.java create mode 100644 src/main/java/arachne/lib/sequences/RepeatableCondition.java create mode 100644 src/main/java/arachne/lib/sequences/Sequence.java create mode 100644 src/main/java/arachne/lib/sequences/SimpleSelector.java create mode 100644 src/main/java/arachne/lib/sequences/WithRequirement.java create mode 100644 src/main/java/arachne/lib/states/State.java create mode 100644 src/main/java/arachne/lib/states/StatefulSubsystem.java create mode 100644 src/main/java/arachne/lib/systems/Subsystem.java create mode 100644 src/main/java/arachne/lib/systems/Tree.java create mode 100644 src/main/java/arachne/tapestry/SequencesTapestry.java create mode 100644 src/test/java/arachne/test/io/GettableBooleanTest.java create mode 100644 src/test/java/arachne/test/logic/LinearMapTest.java diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..e3f6dae --- /dev/null +++ b/.gitignore @@ -0,0 +1,163 @@ +# Created by https://www.gitignore.io/api/c++,java,linux,macos,gradle,windows,visualstudiocode + +### C++ ### +# Prerequisites +*.d + +# Compiled Object files +*.slo +*.lo +*.o +*.obj + +# Precompiled Headers +*.gch +*.pch + +# Compiled Dynamic libraries +*.so +*.dylib +*.dll + +# Fortran module files +*.mod +*.smod + +# Compiled Static libraries +*.lai +*.la +*.a +*.lib + +# Executables +*.exe +*.out +*.app + +### Java ### +# Compiled class file +*.class + +# Log file +*.log + +# BlueJ files +*.ctxt + +# Mobile Tools for Java (J2ME) +.mtj.tmp/ + +# Package Files # +*.jar +*.war +*.nar +*.ear +*.zip +*.tar.gz +*.rar + +# virtual machine crash logs, see http://www.java.com/en/download/help/error_hotspot.xml +hs_err_pid* + +### Linux ### +*~ + +# temporary files which can be created if a process still has a handle open of a deleted file +.fuse_hidden* + +# KDE directory preferences +.directory + +# Linux trash folder which might appear on any partition or disk +.Trash-* + +# .nfs files are created when an open file is removed but is still being accessed +.nfs* + +### macOS ### +# General +.DS_Store +.AppleDouble +.LSOverride + +# Icon must end with two \r +Icon + +# Thumbnails +._* + +# Files that might appear in the root of a volume +.DocumentRevisions-V100 +.fseventsd +.Spotlight-V100 +.TemporaryItems +.Trashes +.VolumeIcon.icns +.com.apple.timemachine.donotpresent + +# Directories potentially created on remote AFP share +.AppleDB +.AppleDesktop +Network Trash Folder +Temporary Items +.apdisk + +### VisualStudioCode ### +.vscode/* +!.vscode/settings.json +!.vscode/tasks.json +!.vscode/launch.json +!.vscode/extensions.json + +### Windows ### +# Windows thumbnail cache files +Thumbs.db +ehthumbs.db +ehthumbs_vista.db + +# Dump file +*.stackdump + +# Folder config file +[Dd]esktop.ini + +# Recycle Bin used on file shares +$RECYCLE.BIN/ + +# Windows Installer files +*.cab +*.msi +*.msix +*.msm +*.msp + +# Windows shortcuts +*.lnk + +### Gradle ### +.gradle +/build/ + +# Ignore Gradle GUI config +gradle-app.setting + +# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored) +!gradle-wrapper.jar + +# Cache of project +.gradletasknamecache + +# # Work around https://youtrack.jetbrains.com/issue/IDEA-116898 +# gradle/wrapper/gradle-wrapper.properties + +# # VS Code Specific Java Settings +.classpath +.project +.settings/ +bin/ + + +# End of https://www.gitignore.io/api/c++,java,linux,macos,gradle,windows,visualstudiocode + +*.json +*gradle* diff --git a/examples/arachne/examples/ArcadeDriveRobot.java b/examples/arachne/examples/ArcadeDriveRobot.java new file mode 100644 index 0000000..a032ea9 --- /dev/null +++ b/examples/arachne/examples/ArcadeDriveRobot.java @@ -0,0 +1,30 @@ +package arachne.examples; + +import arachne.lib.hardware.DifferentialDrivetrain; +import arachne.lib.hardware.SimpleDifferentialDrivetrain; +import arachne.lib.pipeline.SimpleDoubleSource; +import edu.wpi.first.wpilibj.Joystick; +import edu.wpi.first.wpilibj.RobotBase; +import edu.wpi.first.wpilibj.SpeedController; +import edu.wpi.first.wpilibj.Talon; + +public class ArcadeDriveRobot extends RobotBase +{ + public static DifferentialDrivetrain drivetrain = new SimpleDifferentialDrivetrain(false); + + @Override + @SuppressWarnings("resource") + public void startCompetition() { + Joystick controller = new Joystick(0); + + SpeedController + leftMotor = new Talon(0), + rightMotor = new Talon(1); + + new SimpleDoubleSource(controller::getX).attachOutput(drivetrain.arcadeSource.getRotationInput()); + new SimpleDoubleSource(controller::getY).attachOutput(drivetrain.arcadeSource.getSpeedInput()); + + drivetrain.getLeftOutput().attachOutput(leftMotor::set); + drivetrain.getLeftOutput().attachOutput(rightMotor::set); + } +} diff --git a/src/main/java/arachne/lib/ArachneRobot.java b/src/main/java/arachne/lib/ArachneRobot.java new file mode 100644 index 0000000..080eb8c --- /dev/null +++ b/src/main/java/arachne/lib/ArachneRobot.java @@ -0,0 +1,127 @@ +package arachne.lib; + +import arachne.lib.game.GameState; +import arachne.lib.logging.ArachneLogger; +import edu.wpi.first.hal.HAL; +import edu.wpi.first.hal.NotifierJNI; +import edu.wpi.first.wpilibj.DriverStation; +import edu.wpi.first.wpilibj.RobotBase; +import edu.wpi.first.wpilibj.RobotController; +import edu.wpi.first.wpilibj.Watchdog; +import edu.wpi.first.wpilibj.livewindow.LiveWindow; +import edu.wpi.first.wpilibj.shuffleboard.Shuffleboard; +import edu.wpi.first.wpilibj.smartdashboard.SmartDashboard; + +public class ArachneRobot extends RobotBase +{ + public static final double DEFAULT_PERIOD = 0.02; + + protected final double loopPeriod; + protected final Watchdog watchdog; + + // The C pointer to the notifier object. We don't use it directly, it is + // just passed to the JNI bindings. + protected final int notifierHandle = NotifierJNI.initializeNotifier(); + + protected GameState gameState; + + public ArachneRobot() { + this(DEFAULT_PERIOD); + } + + public ArachneRobot(double loopPeriod) { + this.loopPeriod = loopPeriod; + this.watchdog = new Watchdog(loopPeriod, this::handleLoopOverrun); + } + + @Override + public void startCompetition() { + initialize(); + + // Tell the DS that the robot is ready to be enabled + HAL.observeUserProgramStarting(); + + double nextLoopTime = RobotController.getFPGATime() * 1e-6 + loopPeriod; + updateAlarm(nextLoopTime); + + // Loop forever, calling Arachne's execution and state change functions + while(true) { + try { + if(NotifierJNI.waitForNotifierAlarm(notifierHandle) == 0) break; + + nextLoopTime += loopPeriod; + updateAlarm(nextLoopTime); + + loopFunc(); + } + catch(Exception e) { + errorFallback(e); + } + } + } + + /** + * Update the alarm hardware to reflect the next alarm. + */ + protected void updateAlarm(double nextLoopTime) { + NotifierJNI.updateNotifierAlarm(notifierHandle, (long) (nextLoopTime * 1e6)); + } + + protected void loopFunc() { + watchdog.reset(); + + // Determine current state + GameState newState; + if(isDisabled()) newState = GameState.DISABLED; + else if(isAutonomous()) newState = GameState.AUTO; + else if(isOperatorControl()) newState = GameState.TELEOP; + else newState = GameState.TELEOP; + + // Handle state change + if(gameState != newState) { + // Only enable live window and actuator widgets in test mode + if(newState == GameState.TEST) { + LiveWindow.setEnabled(true); + Shuffleboard.enableActuatorWidgets(); + } + else { + LiveWindow.setEnabled(false); + Shuffleboard.disableActuatorWidgets(); + } + + // Handle mode-specific state changes + onStateChange(gameState, newState); + watchdog.addEpoch("onStateChange: " + gameState + " -> " + newState); + } + + // Execute periodic code for state + gameState = newState; + gameState.halObserver.run(); + + execute(gameState); + watchdog.addEpoch("execute: " + gameState); + + watchdog.disable(); + + // Update displayed values + SmartDashboard.updateValues(); + LiveWindow.updateValues(); + Shuffleboard.update(); + + // Warn on loop time overruns + if(watchdog.isExpired()) watchdog.printEpochs(); + } + + protected void initialize() {} + protected void execute(GameState state) {} + protected void onStateChange(GameState oldState, GameState newState) {} + + protected void errorFallback(Exception exception) { + ArachneLogger.getInstance().critical("Error from inside robot's loopFunc(): " + exception.getMessage()); + DriverStation.reportError(exception.getMessage(), exception.getStackTrace()); + } + + protected void handleLoopOverrun() { + DriverStation.reportWarning("Loop time of " + loopPeriod + "s overrun\n", false); + } +} diff --git a/src/main/java/arachne/lib/BindingManager.java b/src/main/java/arachne/lib/BindingManager.java new file mode 100644 index 0000000..1808f90 --- /dev/null +++ b/src/main/java/arachne/lib/BindingManager.java @@ -0,0 +1,17 @@ +package arachne.lib; + +import java.util.function.Supplier; + +import edu.wpi.first.wpilibj.RobotBase; + +public abstract class BindingManager +{ + public static , RobotT extends RobotBase> BindingManagerT create(RobotT robot, Supplier bindingManagerSupplier) { + BindingManagerT bindingManager = bindingManagerSupplier.get(); + bindingManager.createBindings(robot); + + return bindingManager; + } + + protected abstract void createBindings(RobotT robot); +} diff --git a/src/main/java/arachne/lib/HardwareManager.java b/src/main/java/arachne/lib/HardwareManager.java new file mode 100644 index 0000000..6930bbf --- /dev/null +++ b/src/main/java/arachne/lib/HardwareManager.java @@ -0,0 +1,15 @@ +package arachne.lib; + +import java.util.function.Supplier; + +public abstract class HardwareManager +{ + public static T create(Supplier hardwareManagerSupplier) { + T hardwareManager = hardwareManagerSupplier.get(); + hardwareManager.initializeHardware(); + + return hardwareManager; + } + + protected abstract void initializeHardware(); +} diff --git a/src/main/java/arachne/lib/function/BooleanConsumer.java b/src/main/java/arachne/lib/function/BooleanConsumer.java new file mode 100644 index 0000000..5b0c323 --- /dev/null +++ b/src/main/java/arachne/lib/function/BooleanConsumer.java @@ -0,0 +1,16 @@ +package arachne.lib.function; + +import java.util.Objects; + +import arachne.lib.io.SettableBoolean; + +@FunctionalInterface +public interface BooleanConsumer +{ + void accept(boolean value); + + default SettableBoolean andThen(SettableBoolean after) { + Objects.requireNonNull(after); + return (value) -> { accept(value); after.accept(value); }; + } +} diff --git a/src/main/java/arachne/lib/function/BooleanFunction.java b/src/main/java/arachne/lib/function/BooleanFunction.java new file mode 100644 index 0000000..55743b7 --- /dev/null +++ b/src/main/java/arachne/lib/function/BooleanFunction.java @@ -0,0 +1,13 @@ +package arachne.lib.function; + +@FunctionalInterface +public interface BooleanFunction { + + /** + * Applies this function to the given argument. + * + * @param value the function argument + * @return the function result + */ + R apply(boolean value); +} diff --git a/src/main/java/arachne/lib/function/BooleanPredicate.java b/src/main/java/arachne/lib/function/BooleanPredicate.java new file mode 100644 index 0000000..4f0899b --- /dev/null +++ b/src/main/java/arachne/lib/function/BooleanPredicate.java @@ -0,0 +1,69 @@ +package arachne.lib.function; + +import java.util.Objects; + +@FunctionalInterface +public interface BooleanPredicate +{ + /** + * Evaluates this predicate on the given argument. + * + * @param value the input argument + * @return {@code true} if the input argument matches the predicate, + * otherwise {@code false} + */ + boolean test(boolean value); + + /** + * Returns a composed predicate that represents a short-circuiting logical + * AND of this predicate and another. When evaluating the composed + * predicate, if this predicate is {@code false}, then the {@code other} + * predicate is not evaluated. + * + *

Any exceptions thrown during evaluation of either predicate are relayed + * to the caller; if evaluation of this predicate throws an exception, the + * {@code other} predicate will not be evaluated. + * + * @param other a predicate that will be logically-ANDed with this + * predicate + * @return a composed predicate that represents the short-circuiting logical + * AND of this predicate and the {@code other} predicate + * @throws NullPointerException if other is null + */ + default BooleanPredicate and(BooleanPredicate other) { + Objects.requireNonNull(other); + return (value) -> test(value) && other.test(value); + } + + /** + * Returns a predicate that represents the logical negation of this + * predicate. + * + * @return a predicate that represents the logical negation of this + * predicate + */ + default BooleanPredicate negate() { + return (value) -> !test(value); + } + + /** + * Returns a composed predicate that represents a short-circuiting logical + * OR of this predicate and another. When evaluating the composed + * predicate, if this predicate is {@code true}, then the {@code other} + * predicate is not evaluated. + * + *

Any exceptions thrown during evaluation of either predicate are relayed + * to the caller; if evaluation of this predicate throws an exception, the + * {@code other} predicate will not be evaluated. + * + * @param other a predicate that will be logically-ORed with this + * predicate + * @return a composed predicate that represents the short-circuiting logical + * OR of this predicate and the {@code other} predicate + * @throws NullPointerException if other is null + */ + default BooleanPredicate or(BooleanPredicate other) { + Objects.requireNonNull(other); + return (value) -> test(value) || other.test(value); + } +} diff --git a/src/main/java/arachne/lib/game/GameState.java b/src/main/java/arachne/lib/game/GameState.java new file mode 100644 index 0000000..e039e8c --- /dev/null +++ b/src/main/java/arachne/lib/game/GameState.java @@ -0,0 +1,17 @@ +package arachne.lib.game; + +import edu.wpi.first.hal.HAL; + +public enum GameState +{ + DISABLED(HAL::observeUserProgramDisabled), + AUTO(HAL::observeUserProgramAutonomous), + TELEOP(HAL::observeUserProgramTeleop), + TEST(HAL::observeUserProgramTest); + + public final Runnable halObserver; + + private GameState(Runnable halObserver) { + this.halObserver = halObserver; + } +} diff --git a/src/main/java/arachne/lib/hardware/ButtonPanel.java b/src/main/java/arachne/lib/hardware/ButtonPanel.java new file mode 100644 index 0000000..2f9eedc --- /dev/null +++ b/src/main/java/arachne/lib/hardware/ButtonPanel.java @@ -0,0 +1,18 @@ +package arachne.lib.hardware; + +import edu.wpi.first.wpilibj.DriverStation; + +public class ButtonPanel +{ + protected final DriverStation driverStation; + protected final int port; + + public ButtonPanel(int port) { + driverStation = DriverStation.getInstance(); + this.port = port; + } + + protected boolean getButton(int button) { + return driverStation.getStickButton(port, button); + } +} diff --git a/src/main/java/arachne/lib/hardware/DifferentialDrivetrain.java b/src/main/java/arachne/lib/hardware/DifferentialDrivetrain.java new file mode 100644 index 0000000..4b71b52 --- /dev/null +++ b/src/main/java/arachne/lib/hardware/DifferentialDrivetrain.java @@ -0,0 +1,76 @@ +package arachne.lib.hardware; + +import java.util.function.DoubleUnaryOperator; + +import arachne.lib.io.SettableDouble; +import arachne.lib.listeners.DoubleProperty; +import arachne.lib.listeners.SimpleDoubleProperty; +import arachne.lib.pipeline.DoublePipe; +import arachne.lib.pipeline.DoubleSource; +import arachne.lib.pipeline.SimpleDoublePipe; +import arachne.lib.systems.Subsystem; + +public abstract class DifferentialDrivetrain extends Subsystem +{ + protected final DoublePipe leftOutput, rightOutput; + + public final TankSource tankSource; + public final ArcadeSource arcadeSource; + + public DifferentialDrivetrain() { + this(null); + } + + public DifferentialDrivetrain(DoubleUnaryOperator outputModifier) { + tankSource = new TankSource(); + arcadeSource = new ArcadeSource(); + + leftOutput = new SimpleDoublePipe(); + leftOutput.setModifier(outputModifier); + + rightOutput = new SimpleDoublePipe(); + rightOutput.setModifier(outputModifier); + } + + public DoubleSource getLeftOutput() { + return leftOutput; + } + + public DoubleSource getRightOutput() { + return rightOutput; + } + + public class TankSource { + protected final DoublePipe leftInput, rightInput; + + protected TankSource() { + leftInput = new SimpleDoublePipe(); + rightInput = new SimpleDoublePipe(); + } + + public SettableDouble getLeftInput() { + return leftInput; + } + + public SettableDouble getRightInput() { + return rightInput; + } + } + + public class ArcadeSource { + protected final DoubleProperty speedInput, rotationInput; + + protected ArcadeSource() { + speedInput = new SimpleDoubleProperty(); + rotationInput = new SimpleDoubleProperty(); + } + + public SettableDouble getSpeedInput() { + return speedInput; + } + + public SettableDouble getRotationInput() { + return rotationInput; + } + } +} diff --git a/src/main/java/arachne/lib/hardware/SimpleDifferentialDrivetrain.java b/src/main/java/arachne/lib/hardware/SimpleDifferentialDrivetrain.java new file mode 100644 index 0000000..713ee4e --- /dev/null +++ b/src/main/java/arachne/lib/hardware/SimpleDifferentialDrivetrain.java @@ -0,0 +1,56 @@ +package arachne.lib.hardware; + +import java.util.function.DoublePredicate; + +import arachne.lib.io.SettableBoolean; +import arachne.lib.listeners.BooleanProperty; +import arachne.lib.listeners.DoubleChangeHandler; +import arachne.lib.listeners.SimpleBooleanProperty; +import arachne.lib.logic.ArachneMath; + +public class SimpleDifferentialDrivetrain extends DifferentialDrivetrain +{ + protected final BooleanProperty isTankState; + + public SimpleDifferentialDrivetrain() { + this(true, true); + } + + public SimpleDifferentialDrivetrain(boolean isInitiallyTankDrive, boolean squareInputs) { + this.isTankState = new SimpleBooleanProperty(isInitiallyTankDrive); + } + + @Override + protected void initialize() { + initializeArcadeBindings(); + initializeTankBindings(); + } + + protected void initializeArcadeBindings() { + DoubleChangeHandler feedArcadeDrive = (oldValue, newValue) -> { + if(!isTankState.get()) arcadeDrive(arcadeSource.speedInput.get(), arcadeSource.rotationInput.get()); + }; + + arcadeSource.speedInput.attach(feedArcadeDrive); + arcadeSource.rotationInput.attach(feedArcadeDrive); + } + + protected void initializeTankBindings() { + DoublePredicate tankModeFilter = (value) -> isTankState.get(); + + tankSource.leftInput.addFilter(tankModeFilter); + tankSource.leftInput.attachOutput(leftOutput); + + tankSource.rightInput.addFilter(tankModeFilter); + tankSource.rightInput.attachOutput(rightOutput); + } + + protected void arcadeDrive(double speed, double rotation) { + leftOutput.accept(ArachneMath.inBounds(speed + rotation, -1, 1)); + rightOutput.accept(ArachneMath.inBounds(speed - rotation, -1, 1)); + } + + public SettableBoolean getTankStateInput() { + return isTankState; + } +} diff --git a/src/main/java/arachne/lib/hardware/adaptors/BlinkinStateAdaptor.java b/src/main/java/arachne/lib/hardware/adaptors/BlinkinStateAdaptor.java new file mode 100644 index 0000000..cb84d1b --- /dev/null +++ b/src/main/java/arachne/lib/hardware/adaptors/BlinkinStateAdaptor.java @@ -0,0 +1,59 @@ +package arachne.lib.hardware.adaptors; + +import arachne.lib.io.Gettable; +import arachne.lib.io.Settable; +import edu.wpi.first.wpilibj.Spark; + +public class BlinkinStateAdaptor implements Gettable, + Settable +{ + public static enum Pattern + { + YELLOW(0.57),// HOT_PINK(0.57), + DARK_RED(0.59), + RED(0.61), + HOT_PINK(0.63),// RED_ORANGE(0.63), + PINK(0.65),// ORANGE(0.65), + PALE_VIOLET(0.67),// GOLD(0.67), + VIOLET(0.69),// YELLOW(0.69), + PURPLE(0.71),// LAWN_GREEN(0.71), + LIGHT_PURPLE(0.73),// LIME(0.73), + BLUE_PURPLE(0.75),// DARK_GREEN(0.75), + DARK_BLUE(0.77),// GREEN(0.77), + BLUE(0.79),// BLUE_GREEN(0.79), + LIGHT_BLUE(0.81),// AQUA(0.81), + PALE_BLUE(0.83),// SKY_BLUE(0.83), + AQUA(0.85),// DARK_BLUE(0.85), + TURQUOISE(0.87),// BLUE(0.87), + GREEN(0.89),// BLUE_VIOLET(0.89), + LIME(0.91),// VIOLET(0.91), + WHITE(0.93), + GRAY(0.95), + DARK_GRAY(0.97), + BLACK(0.99); + + protected final double value; + + private Pattern(double value) { + this.value = value; + } + } + + protected final Spark controller; + protected Pattern pattern; + + public BlinkinStateAdaptor(int channel) { + this.controller = new Spark(channel); + } + + @Override + public Pattern get() { + return pattern; + } + + @Override + public void accept(Pattern pattern) { + this.pattern = pattern; + this.controller.set(pattern.value); + } +} diff --git a/src/main/java/arachne/lib/hardware/adaptors/DoubleSolenoidBooleanAdaptor.java b/src/main/java/arachne/lib/hardware/adaptors/DoubleSolenoidBooleanAdaptor.java new file mode 100644 index 0000000..ce74287 --- /dev/null +++ b/src/main/java/arachne/lib/hardware/adaptors/DoubleSolenoidBooleanAdaptor.java @@ -0,0 +1,24 @@ +package arachne.lib.hardware.adaptors; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.SettableBoolean; +import edu.wpi.first.wpilibj.DoubleSolenoid; +import edu.wpi.first.wpilibj.DoubleSolenoid.Value; + +public class DoubleSolenoidBooleanAdaptor implements GettableBoolean, SettableBoolean +{ + protected DoubleSolenoid solenoid; + + public DoubleSolenoidBooleanAdaptor(DoubleSolenoid solenoid) { + this.solenoid = solenoid; + } + + public boolean get() { + return solenoid.get() == Value.kForward; + } + + @Override + public void accept(boolean value) { + solenoid.set(value ? Value.kForward : Value.kReverse); + } +} diff --git a/src/main/java/arachne/lib/immutables/DoublePair.java b/src/main/java/arachne/lib/immutables/DoublePair.java new file mode 100644 index 0000000..aa56894 --- /dev/null +++ b/src/main/java/arachne/lib/immutables/DoublePair.java @@ -0,0 +1,19 @@ +package arachne.lib.immutables; + +public class DoublePair +{ + protected double first, second; + + public DoublePair(double first, double second) { + this.first = first; + this.second = second; + } + + public double getFirst() { + return first; + } + + public double getSecond() { + return second; + } +} diff --git a/src/main/java/arachne/lib/immutables/FlaggedBooleanValue.java b/src/main/java/arachne/lib/immutables/FlaggedBooleanValue.java new file mode 100644 index 0000000..d96c0f6 --- /dev/null +++ b/src/main/java/arachne/lib/immutables/FlaggedBooleanValue.java @@ -0,0 +1,20 @@ +package arachne.lib.immutables; + +public class FlaggedBooleanValue +{ + protected boolean value; + protected boolean flag; + + public FlaggedBooleanValue(boolean value, boolean flag) { + this.value = value; + this.flag = flag; + } + + public boolean getValue() { + return value; + } + + public boolean getFlag() { + return flag; + } +} diff --git a/src/main/java/arachne/lib/immutables/FlaggedDoubleValue.java b/src/main/java/arachne/lib/immutables/FlaggedDoubleValue.java new file mode 100644 index 0000000..746dd9d --- /dev/null +++ b/src/main/java/arachne/lib/immutables/FlaggedDoubleValue.java @@ -0,0 +1,20 @@ +package arachne.lib.immutables; + +public class FlaggedDoubleValue +{ + protected double value; + protected boolean flag; + + public FlaggedDoubleValue(double value, boolean flag) { + this.value = value; + this.flag = flag; + } + + public double getValue() { + return value; + } + + public boolean getFlag() { + return flag; + } +} diff --git a/src/main/java/arachne/lib/immutables/FlaggedValue.java b/src/main/java/arachne/lib/immutables/FlaggedValue.java new file mode 100644 index 0000000..207fbcf --- /dev/null +++ b/src/main/java/arachne/lib/immutables/FlaggedValue.java @@ -0,0 +1,20 @@ +package arachne.lib.immutables; + +public class FlaggedValue +{ + protected T value; + protected boolean flag; + + public FlaggedValue(T value, boolean flag) { + this.value = value; + this.flag = flag; + } + + public T getValue() { + return value; + } + + public boolean getFlag() { + return flag; + } +} diff --git a/src/main/java/arachne/lib/immutables/Pair.java b/src/main/java/arachne/lib/immutables/Pair.java new file mode 100644 index 0000000..c343323 --- /dev/null +++ b/src/main/java/arachne/lib/immutables/Pair.java @@ -0,0 +1,20 @@ +package arachne.lib.immutables; + +public class Pair +{ + protected FirstT first; + protected SecondT second; + + public Pair(FirstT first, SecondT second) { + this.first = first; + this.second = second; + } + + public FirstT getFirst() { + return first; + } + + public SecondT getSecond() { + return second; + } +} diff --git a/src/main/java/arachne/lib/immutables/Triple.java b/src/main/java/arachne/lib/immutables/Triple.java new file mode 100644 index 0000000..bf45af1 --- /dev/null +++ b/src/main/java/arachne/lib/immutables/Triple.java @@ -0,0 +1,26 @@ +package arachne.lib.immutables; + +public class Triple +{ + protected FirstT first; + protected SecondT second; + protected ThirdT third; + + public Triple(FirstT first, SecondT second, ThirdT third) { + this.first = first; + this.second = second; + this.third = third; + } + + public FirstT getFirst() { + return first; + } + + public SecondT getSecond() { + return second; + } + + public ThirdT getThird() { + return third; + } +} diff --git a/src/main/java/arachne/lib/io/Gettable.java b/src/main/java/arachne/lib/io/Gettable.java new file mode 100644 index 0000000..b98b64f --- /dev/null +++ b/src/main/java/arachne/lib/io/Gettable.java @@ -0,0 +1,21 @@ +package arachne.lib.io; + +import java.util.function.Predicate; +import java.util.function.Supplier; +import java.util.function.UnaryOperator; + +@FunctionalInterface +public interface Gettable extends Supplier +{ + default GettableBoolean is(T target) { + return () -> get() == target; + } + + default GettableBoolean is(Predicate predicate) { + return () -> predicate.test(get()); + } + + default Gettable withModifier(UnaryOperator modifier) { + return () -> modifier.apply(get()); + } +} diff --git a/src/main/java/arachne/lib/io/GettableBoolean.java b/src/main/java/arachne/lib/io/GettableBoolean.java new file mode 100644 index 0000000..dcfae78 --- /dev/null +++ b/src/main/java/arachne/lib/io/GettableBoolean.java @@ -0,0 +1,51 @@ +package arachne.lib.io; + +import java.util.function.BooleanSupplier; + +import arachne.lib.function.BooleanPredicate; +import arachne.lib.sequences.Action; +import arachne.lib.sequences.Actionable; +import arachne.lib.sequences.HostAction; + +@FunctionalInterface +public interface GettableBoolean extends BooleanSupplier, Actionable +{ + GettableBoolean TRUE = () -> true, FALSE = () -> false; + + boolean get(); + + @Override + default boolean getAsBoolean() { + return get(); + } + + @Override + default Action asAction(HostAction host) { + return new Action(host) { + @Override + protected boolean isFinished() { + return GettableBoolean.this.get(); + } + }; + } + + default GettableBoolean is(boolean target) { + return () -> get() == target; + } + + default GettableBoolean is(BooleanPredicate predicate) { + return () -> predicate.test(get()); + } + + default GettableBoolean and(GettableBoolean other) { + return () -> this.get() && other.get(); + } + + default GettableBoolean or(GettableBoolean other) { + return () -> this.get() || other.get(); + } + + default GettableBoolean xor(GettableBoolean other) { + return () -> this.get() != other.get(); + } +} diff --git a/src/main/java/arachne/lib/io/GettableDouble.java b/src/main/java/arachne/lib/io/GettableDouble.java new file mode 100644 index 0000000..a1a510e --- /dev/null +++ b/src/main/java/arachne/lib/io/GettableDouble.java @@ -0,0 +1,52 @@ +package arachne.lib.io; + +import java.util.function.DoublePredicate; +import java.util.function.DoubleSupplier; +import java.util.function.DoubleUnaryOperator; + +import edu.wpi.first.wpilibj.PIDSource; +import edu.wpi.first.wpilibj.PIDSourceType; + +@FunctionalInterface +public interface GettableDouble extends DoubleSupplier +{ + double get(); + + @Override + default double getAsDouble() { + return get(); + } + + default GettableBoolean is(double target) { + return () -> get() == target; + } + + default GettableBoolean is(DoublePredicate predicate) { + return () -> predicate.test(get()); + } + + default GettableDouble withModifier(DoubleUnaryOperator modifier) { + return () -> modifier.applyAsDouble(get()); + } + + default PIDSource asPIDSource(PIDSourceType pidSource) { + return new PIDSource() { + private PIDSourceType type = pidSource; + + @Override + public double pidGet() { + return GettableDouble.this.get(); + } + + @Override + public PIDSourceType getPIDSourceType() { + return type; + } + + @Override + public void setPIDSourceType(PIDSourceType pidSource) { + this.type = pidSource; + } + }; + } +} diff --git a/src/main/java/arachne/lib/io/Settable.java b/src/main/java/arachne/lib/io/Settable.java new file mode 100644 index 0000000..13e84d5 --- /dev/null +++ b/src/main/java/arachne/lib/io/Settable.java @@ -0,0 +1,9 @@ +package arachne.lib.io; + +import java.util.function.Consumer; + +@FunctionalInterface +public interface Settable extends Consumer +{ + // Empty, exists for future features +} diff --git a/src/main/java/arachne/lib/io/SettableBoolean.java b/src/main/java/arachne/lib/io/SettableBoolean.java new file mode 100644 index 0000000..1a5dbfb --- /dev/null +++ b/src/main/java/arachne/lib/io/SettableBoolean.java @@ -0,0 +1,9 @@ +package arachne.lib.io; + +import arachne.lib.function.BooleanConsumer; + +@FunctionalInterface +public interface SettableBoolean extends BooleanConsumer +{ + // Empty, exists for future features +} diff --git a/src/main/java/arachne/lib/io/SettableDouble.java b/src/main/java/arachne/lib/io/SettableDouble.java new file mode 100644 index 0000000..9ab64f9 --- /dev/null +++ b/src/main/java/arachne/lib/io/SettableDouble.java @@ -0,0 +1,14 @@ +package arachne.lib.io; + +import java.util.function.DoubleConsumer; + +import edu.wpi.first.wpilibj.PIDOutput; + +@FunctionalInterface +public interface SettableDouble extends DoubleConsumer, PIDOutput +{ + @Override + default void pidWrite(double output) { + accept(output); + } +} diff --git a/src/main/java/arachne/lib/listeners/BooleanChangeHandler.java b/src/main/java/arachne/lib/listeners/BooleanChangeHandler.java new file mode 100644 index 0000000..c53e8fc --- /dev/null +++ b/src/main/java/arachne/lib/listeners/BooleanChangeHandler.java @@ -0,0 +1,7 @@ +package arachne.lib.listeners; + +@FunctionalInterface +public interface BooleanChangeHandler +{ + void onChange(boolean oldValue, boolean newValue); +} diff --git a/src/main/java/arachne/lib/listeners/BooleanHook.java b/src/main/java/arachne/lib/listeners/BooleanHook.java new file mode 100644 index 0000000..ad407b0 --- /dev/null +++ b/src/main/java/arachne/lib/listeners/BooleanHook.java @@ -0,0 +1,8 @@ +package arachne.lib.listeners; + +public interface BooleanHook +{ + boolean attach(BooleanChangeHandler changeHandler); + boolean detach(BooleanChangeHandler changeHandler); + void detachAll(); +} diff --git a/src/main/java/arachne/lib/listeners/BooleanProperty.java b/src/main/java/arachne/lib/listeners/BooleanProperty.java new file mode 100644 index 0000000..3c0a2ee --- /dev/null +++ b/src/main/java/arachne/lib/listeners/BooleanProperty.java @@ -0,0 +1,14 @@ +package arachne.lib.listeners; + +import arachne.lib.io.SettableBoolean; + +public abstract class BooleanProperty extends ReadOnlyBooleanProperty implements SettableBoolean +{ + @Override + public void accept(boolean value) { + fireChange(get(), value); + _accept(value); + } + + protected abstract void _accept(boolean value); +} diff --git a/src/main/java/arachne/lib/listeners/ChangeHandler.java b/src/main/java/arachne/lib/listeners/ChangeHandler.java new file mode 100644 index 0000000..0461bf2 --- /dev/null +++ b/src/main/java/arachne/lib/listeners/ChangeHandler.java @@ -0,0 +1,7 @@ +package arachne.lib.listeners; + +@FunctionalInterface +public interface ChangeHandler +{ + void onChange(T oldValue, T newValue); +} diff --git a/src/main/java/arachne/lib/listeners/DoubleChangeHandler.java b/src/main/java/arachne/lib/listeners/DoubleChangeHandler.java new file mode 100644 index 0000000..fa5d7d0 --- /dev/null +++ b/src/main/java/arachne/lib/listeners/DoubleChangeHandler.java @@ -0,0 +1,7 @@ +package arachne.lib.listeners; + +@FunctionalInterface +public interface DoubleChangeHandler +{ + void onChange(double oldValue, double newValue); +} diff --git a/src/main/java/arachne/lib/listeners/DoubleHook.java b/src/main/java/arachne/lib/listeners/DoubleHook.java new file mode 100644 index 0000000..0a0715f --- /dev/null +++ b/src/main/java/arachne/lib/listeners/DoubleHook.java @@ -0,0 +1,8 @@ +package arachne.lib.listeners; + +public interface DoubleHook +{ + boolean attach(DoubleChangeHandler changeHandler); + boolean detach(DoubleChangeHandler changeHandler); + void detachAll(); +} diff --git a/src/main/java/arachne/lib/listeners/DoubleProperty.java b/src/main/java/arachne/lib/listeners/DoubleProperty.java new file mode 100644 index 0000000..9f0c15d --- /dev/null +++ b/src/main/java/arachne/lib/listeners/DoubleProperty.java @@ -0,0 +1,56 @@ +package arachne.lib.listeners; + +import arachne.lib.io.SettableDouble; +import edu.wpi.first.wpilibj.SpeedController; + +public abstract class DoubleProperty extends ReadOnlyDoubleProperty implements SettableDouble +{ + @Override + public void accept(double value) { + fireChange(get(), value); + _accept(value); + } + + protected abstract void _accept(double value); + + public SpeedController asSpeedController() { + return new SpeedController() { + private boolean isInverted = false; + + @Override + public double get() { + return DoubleProperty.this.get(); + } + + @Override + public void set(double speed) { + DoubleProperty.this.accept(isInverted ? -speed : speed); + } + + @Override + public void pidWrite(double output) { + DoubleProperty.this.pidWrite(output); + } + + @Override + public boolean getInverted() { + return isInverted; + } + + @Override + public void setInverted(boolean isInverted) { + this.isInverted = isInverted; + } + + @Override + public void disable() { + DoubleProperty.this.accept(0); + } + + @Override + public void stopMotor() { + disable(); + } + }; + } +} diff --git a/src/main/java/arachne/lib/listeners/Hook.java b/src/main/java/arachne/lib/listeners/Hook.java new file mode 100644 index 0000000..a6161af --- /dev/null +++ b/src/main/java/arachne/lib/listeners/Hook.java @@ -0,0 +1,8 @@ +package arachne.lib.listeners; + +public interface Hook +{ + boolean attach(ChangeHandler changeHandler); + boolean detach(ChangeHandler changeHandler); + void detachAll(); +} diff --git a/src/main/java/arachne/lib/listeners/Property.java b/src/main/java/arachne/lib/listeners/Property.java new file mode 100644 index 0000000..d772b2b --- /dev/null +++ b/src/main/java/arachne/lib/listeners/Property.java @@ -0,0 +1,14 @@ +package arachne.lib.listeners; + +import arachne.lib.io.Settable; + +public abstract class Property extends ReadOnlyProperty implements Settable +{ + @Override + public void accept(T value) { + fireChange(get(), value); + _accept(value); + } + + protected abstract void _accept(T value); +} diff --git a/src/main/java/arachne/lib/listeners/ReadOnlyBooleanProperty.java b/src/main/java/arachne/lib/listeners/ReadOnlyBooleanProperty.java new file mode 100644 index 0000000..31a2965 --- /dev/null +++ b/src/main/java/arachne/lib/listeners/ReadOnlyBooleanProperty.java @@ -0,0 +1,34 @@ +package arachne.lib.listeners; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.GettableBoolean; + +public abstract class ReadOnlyBooleanProperty implements GettableBoolean, BooleanHook +{ + protected final Set changeHandlers; + + public ReadOnlyBooleanProperty() { + this.changeHandlers = new LinkedHashSet(); + } + + @Override + public boolean attach(BooleanChangeHandler changeHandler) { + return changeHandlers.add(changeHandler); + } + + @Override + public boolean detach(BooleanChangeHandler changeHandler) { + return changeHandlers.remove(changeHandler); + } + + @Override + public void detachAll() { + changeHandlers.clear(); + } + + protected void fireChange(boolean oldValue, boolean newValue) { + for(BooleanChangeHandler handler : changeHandlers) handler.onChange(oldValue, newValue); + } +} diff --git a/src/main/java/arachne/lib/listeners/ReadOnlyDoubleProperty.java b/src/main/java/arachne/lib/listeners/ReadOnlyDoubleProperty.java new file mode 100644 index 0000000..93c1402 --- /dev/null +++ b/src/main/java/arachne/lib/listeners/ReadOnlyDoubleProperty.java @@ -0,0 +1,34 @@ +package arachne.lib.listeners; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.GettableDouble; + +public abstract class ReadOnlyDoubleProperty implements GettableDouble, DoubleHook +{ + protected final Set changeHandlers; + + public ReadOnlyDoubleProperty() { + this.changeHandlers = new LinkedHashSet(); + } + + @Override + public boolean attach(DoubleChangeHandler changeHandler) { + return changeHandlers.add(changeHandler); + } + + @Override + public boolean detach(DoubleChangeHandler changeHandler) { + return changeHandlers.remove(changeHandler); + } + + @Override + public void detachAll() { + changeHandlers.clear(); + } + + protected void fireChange(double oldValue, double newValue) { + for(DoubleChangeHandler handler : changeHandlers) handler.onChange(oldValue, newValue); + } +} diff --git a/src/main/java/arachne/lib/listeners/ReadOnlyProperty.java b/src/main/java/arachne/lib/listeners/ReadOnlyProperty.java new file mode 100644 index 0000000..a249ffe --- /dev/null +++ b/src/main/java/arachne/lib/listeners/ReadOnlyProperty.java @@ -0,0 +1,34 @@ +package arachne.lib.listeners; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.Gettable; + +public abstract class ReadOnlyProperty implements Gettable, Hook +{ + protected final Set> changeHandlers; + + public ReadOnlyProperty() { + this.changeHandlers = new LinkedHashSet>(); + } + + @Override + public boolean attach(ChangeHandler changeHandler) { + return changeHandlers.add(changeHandler); + } + + @Override + public boolean detach(ChangeHandler changeHandler) { + return changeHandlers.remove(changeHandler); + } + + @Override + public void detachAll() { + changeHandlers.clear(); + } + + protected void fireChange(T oldValue, T newValue) { + for(ChangeHandler handler : changeHandlers) handler.onChange(oldValue, newValue); + } +} diff --git a/src/main/java/arachne/lib/listeners/ReadOnlySignal.java b/src/main/java/arachne/lib/listeners/ReadOnlySignal.java new file mode 100644 index 0000000..f2bb8c1 --- /dev/null +++ b/src/main/java/arachne/lib/listeners/ReadOnlySignal.java @@ -0,0 +1,25 @@ +package arachne.lib.listeners; + +import java.util.LinkedHashSet; +import java.util.Set; + +public class ReadOnlySignal +{ + protected Set handlers; + + public ReadOnlySignal() { + this.handlers = new LinkedHashSet(); + } + + public boolean attach(Runnable handler) { + return handlers.add(handler); + } + + public boolean detach(Runnable handler) { + return handlers.remove(handler); + } + + public void detachAll() { + handlers.clear(); + } +} diff --git a/src/main/java/arachne/lib/listeners/Signal.java b/src/main/java/arachne/lib/listeners/Signal.java new file mode 100644 index 0000000..3b3810a --- /dev/null +++ b/src/main/java/arachne/lib/listeners/Signal.java @@ -0,0 +1,8 @@ +package arachne.lib.listeners; + +public class Signal extends ReadOnlySignal +{ + public void fire() { + for(Runnable handler : handlers) handler.run(); + } +} diff --git a/src/main/java/arachne/lib/listeners/SimpleBooleanProperty.java b/src/main/java/arachne/lib/listeners/SimpleBooleanProperty.java new file mode 100644 index 0000000..373047c --- /dev/null +++ b/src/main/java/arachne/lib/listeners/SimpleBooleanProperty.java @@ -0,0 +1,25 @@ +package arachne.lib.listeners; + +public class SimpleBooleanProperty extends BooleanProperty +{ + protected boolean value; + + public SimpleBooleanProperty() { + this(false); + } + + public SimpleBooleanProperty(boolean initialValue) { + super(); + this.value = initialValue; + } + + @Override + public boolean get() { + return value; + } + + @Override + protected void _accept(boolean value) { + this.value = value; + } +} diff --git a/src/main/java/arachne/lib/listeners/SimpleDoubleProperty.java b/src/main/java/arachne/lib/listeners/SimpleDoubleProperty.java new file mode 100644 index 0000000..dc56f0a --- /dev/null +++ b/src/main/java/arachne/lib/listeners/SimpleDoubleProperty.java @@ -0,0 +1,25 @@ +package arachne.lib.listeners; + +public class SimpleDoubleProperty extends DoubleProperty +{ + protected double value; + + public SimpleDoubleProperty() { + this(0); + } + + public SimpleDoubleProperty(double initialValue) { + super(); + this.value = initialValue; + } + + @Override + public double get() { + return value; + } + + @Override + protected void _accept(double value) { + this.value = value; + } +} diff --git a/src/main/java/arachne/lib/listeners/SimpleProperty.java b/src/main/java/arachne/lib/listeners/SimpleProperty.java new file mode 100644 index 0000000..16b929d --- /dev/null +++ b/src/main/java/arachne/lib/listeners/SimpleProperty.java @@ -0,0 +1,25 @@ +package arachne.lib.listeners; + +public class SimpleProperty extends Property +{ + protected T value; + + public SimpleProperty() { + this(null); + } + + public SimpleProperty(T initialValue) { + super(); + this.value = initialValue; + } + + @Override + public T get() { + return value; + } + + @Override + protected void _accept(T value) { + this.value = value; + } +} diff --git a/src/main/java/arachne/lib/listeners/SimpleReadOnlyBooleanProperty.java b/src/main/java/arachne/lib/listeners/SimpleReadOnlyBooleanProperty.java new file mode 100644 index 0000000..3828a6e --- /dev/null +++ b/src/main/java/arachne/lib/listeners/SimpleReadOnlyBooleanProperty.java @@ -0,0 +1,20 @@ +package arachne.lib.listeners; + +public class SimpleReadOnlyBooleanProperty extends ReadOnlyBooleanProperty +{ + protected boolean value; + + public SimpleReadOnlyBooleanProperty() { + this(false); + } + + public SimpleReadOnlyBooleanProperty(boolean initialValue) { + super(); + this.value = initialValue; + } + + @Override + public boolean get() { + return value; + } +} diff --git a/src/main/java/arachne/lib/listeners/SimpleReadOnlyDoubleProperty.java b/src/main/java/arachne/lib/listeners/SimpleReadOnlyDoubleProperty.java new file mode 100644 index 0000000..4adf9ef --- /dev/null +++ b/src/main/java/arachne/lib/listeners/SimpleReadOnlyDoubleProperty.java @@ -0,0 +1,20 @@ +package arachne.lib.listeners; + +public class SimpleReadOnlyDoubleProperty extends ReadOnlyDoubleProperty +{ + protected double value; + + public SimpleReadOnlyDoubleProperty() { + this(0); + } + + public SimpleReadOnlyDoubleProperty(double initialValue) { + super(); + this.value = initialValue; + } + + @Override + public double get() { + return value; + } +} diff --git a/src/main/java/arachne/lib/listeners/SimpleReadOnlyProperty.java b/src/main/java/arachne/lib/listeners/SimpleReadOnlyProperty.java new file mode 100644 index 0000000..ccda44b --- /dev/null +++ b/src/main/java/arachne/lib/listeners/SimpleReadOnlyProperty.java @@ -0,0 +1,20 @@ +package arachne.lib.listeners; + +public class SimpleReadOnlyProperty extends ReadOnlyProperty +{ + protected T value; + + public SimpleReadOnlyProperty() { + this(null); + } + + public SimpleReadOnlyProperty(T initialValue) { + super(); + this.value = initialValue; + } + + @Override + public T get() { + return value; + } +} diff --git a/src/main/java/arachne/lib/logging/ArachneLogger.java b/src/main/java/arachne/lib/logging/ArachneLogger.java new file mode 100644 index 0000000..776a5c7 --- /dev/null +++ b/src/main/java/arachne/lib/logging/ArachneLogger.java @@ -0,0 +1,39 @@ +package arachne.lib.logging; + +import java.io.PrintStream; + +public final class ArachneLogger +{ + private static ArachneLogger instance; + private final PrintStream infoStream, errorStream; + + public ArachneLogger(PrintStream stream) { + this.infoStream = this.errorStream = stream; + } + + public ArachneLogger(PrintStream infoStream, PrintStream errorStream) { + this.infoStream = infoStream; + this.errorStream = errorStream; + } + + public static ArachneLogger getInstance() { + if(instance == null) instance = new ArachneLogger(System.out, System.err); + return instance; + } + + public void info(String message) { + infoStream.println("[INFO] " + message); + } + + public void warn(String message) { + infoStream.println("[WARN] " + message); + } + + public void error(String message) { + errorStream.println("[ERROR] " + message); + } + + public void critical(String message) { + errorStream.println("[CRITICAL] " + message); + } +} diff --git a/src/main/java/arachne/lib/logic/ArachneMath.java b/src/main/java/arachne/lib/logic/ArachneMath.java new file mode 100644 index 0000000..21624ab --- /dev/null +++ b/src/main/java/arachne/lib/logic/ArachneMath.java @@ -0,0 +1,13 @@ +package arachne.lib.logic; + +public class ArachneMath +{ + public static double inBounds(double value, double min, double max) { + return Math.min(Math.max(value, min), max); + } + + public static double signedPow(double value, double exponent) { + if(value < 0) return -Math.pow(-value, exponent); + return Math.pow(value, exponent); + } +} diff --git a/src/main/java/arachne/lib/logic/BooleanOperation.java b/src/main/java/arachne/lib/logic/BooleanOperation.java new file mode 100644 index 0000000..0eb959f --- /dev/null +++ b/src/main/java/arachne/lib/logic/BooleanOperation.java @@ -0,0 +1,102 @@ +package arachne.lib.logic; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.sequences.RepeatableCondition; + +public class BooleanOperation +{ + public static GettableBoolean and(GettableBoolean... booleans) { + return () -> { + for(GettableBoolean bool : booleans) { + if(!bool.get()) return false; + } + + return true; + }; + } + + public static GettableBoolean or(GettableBoolean... booleans) { + return () -> { + for(GettableBoolean bool : booleans) { + if(bool.get()) return true; + } + + return false; + }; + } + + public static GettableBoolean not(GettableBoolean bool) { + return () -> !bool.get(); + } + + public static GettableBoolean count(int target, GettableBoolean... booleans) { + return count(target, target, booleans); + } + + public static GettableBoolean count(int min, int max, GettableBoolean... booleans) { + return () -> { + int count = 0; + + for(GettableBoolean bool : booleans) { + if(bool.get()) count++; + } + + return count >= min && count <= max; + }; + } + + public static GettableBoolean once(GettableBoolean... booleans) { + return once(true, booleans); + } + + public static GettableBoolean once(boolean resetOnCompletion, GettableBoolean... booleans) { + return new RepeatableCondition(resetOnCompletion) { + private final boolean[] occurred = new boolean[booleans.length]; + + @Override + protected void initialize() { + for(int i = 0; i < occurred.length; i++) occurred[i] = false; + } + + @Override + protected boolean condition() { + for(int i = 0; i < occurred.length; i++) { + if(!booleans[i].get() && !occurred[i]) return false; + else occurred[i] = true; + } + + return true; + } + }; + } + + public static GettableBoolean inOrder(GettableBoolean... booleans) { + return inOrder(true, booleans); + } + + /** + * Returns a {@link RepeatableCondition} for a sequence of requirements + * + * @param resetOnCompletion Whether the RepeatableCondition should reset to an un-initisalised state after completion + * @param booleans The sequence of requirements, in order + * + * @return A RepeatableCondition for the sequence of requirements + */ + public static GettableBoolean inOrder(boolean resetOnCompletion, GettableBoolean... booleans) { + return new RepeatableCondition(resetOnCompletion) { + private int index; + + @Override + protected void initialize() { + index = 0; + } + + @Override + protected boolean condition() { + while(index < booleans.length && booleans[index].get()) index++; + + return index == booleans.length; + } + }; + } +} diff --git a/src/main/java/arachne/lib/logic/DoubleComparison.java b/src/main/java/arachne/lib/logic/DoubleComparison.java new file mode 100644 index 0000000..570a880 --- /dev/null +++ b/src/main/java/arachne/lib/logic/DoubleComparison.java @@ -0,0 +1,54 @@ +package arachne.lib.logic; + +import java.util.function.DoublePredicate; + +@FunctionalInterface +public interface DoubleComparison +{ + public boolean compare(double a, double b); + + public static DoublePredicate greaterThan(double val) { + return d -> d > val; + } + + public static DoublePredicate lessThan(double val) { + return d -> d < val; + } + + public static DoublePredicate greaterThanOrEqualTo(double val) { + return d -> d >= val; + } + + public static DoublePredicate lessThanOrEqualTo(double val) { + return d -> d <= val; + } + + public static DoublePredicate equal(double val) { + return d -> d == val; + } + + public static DoublePredicate between(double min, double max) { + return d -> d >= min && d <= max; + } + + public static Within within(double tolerance) { + return new Within(tolerance); + } + + public static class Within implements DoubleComparison + { + protected final double tolerance; + + protected Within(double tolerance) { + this.tolerance = tolerance; + } + + public boolean compare(double a, double b) { + return Math.abs(a - b) <= tolerance; + } + + public DoublePredicate of(double val) { + return d -> compare(d, val); + } + } +} diff --git a/src/main/java/arachne/lib/logic/LinearMap.java b/src/main/java/arachne/lib/logic/LinearMap.java new file mode 100644 index 0000000..2607eee --- /dev/null +++ b/src/main/java/arachne/lib/logic/LinearMap.java @@ -0,0 +1,66 @@ +package arachne.lib.logic; + +import java.util.function.DoubleFunction; +import java.util.function.DoubleUnaryOperator; + +import arachne.lib.immutables.FlaggedDoubleValue; + +// TODO Refactor to Tapestry +public class LinearMap +{ + protected final double min, max; + protected final boolean isBounded; + + protected LinearMap(double min, double max, boolean isBounded) { + this.min = min; + this.max = max; + this.isBounded = isBounded; + } + + public static LinearMap map(double min, double max) { + return new LinearMap(min, max, false); + } + + public Until to(double start, double finish) { + return new Until((d) -> { + if(isBounded) d = ArachneMath.inBounds(d, this.min, this.max); + + return (d - this.min) * (finish - start) / (this.max - this.min) + start; + }); + } + + public static final int + REACHES_MIN = 1, + REACHES_MAX = 1 << 1; + + public class Until implements DoubleUnaryOperator { + protected final DoubleUnaryOperator mapping; + + protected Until(DoubleUnaryOperator mapping) { + this.mapping = mapping; + } + + @Override + public double applyAsDouble(double operand) { + return mapping.applyAsDouble(operand); + } + + public DoubleFunction until(int conditions) { + return (value) -> { + boolean flag = false; + + if((conditions & REACHES_MIN) > 0 && value <= LinearMap.this.min) { + value = LinearMap.this.min; + flag = true; + } + + if((conditions & REACHES_MAX) > 0 && value >= LinearMap.this.max) { + value = LinearMap.this.max; + flag = true; + } + + return new FlaggedDoubleValue(applyAsDouble(value), flag); + }; + } + } +} diff --git a/src/main/java/arachne/lib/pipeline/AbstractBooleanSource.java b/src/main/java/arachne/lib/pipeline/AbstractBooleanSource.java new file mode 100644 index 0000000..5544973 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/AbstractBooleanSource.java @@ -0,0 +1,40 @@ +package arachne.lib.pipeline; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.SettableBoolean; + +public abstract class AbstractBooleanSource implements BooleanSource +{ + protected Set outputs; + + public AbstractBooleanSource() { + this.outputs = new LinkedHashSet(); + } + + protected abstract boolean getOutputValue(); + + @Override + public SettableT attachOutput(SettableT settable) { + outputs.add(settable); + return settable; + } + + @Override + public boolean detachOutput(SettableBoolean settable) { + return outputs.remove(settable); + } + + @Override + public void detachAllOutputs() { + outputs.clear(); + } + + @Override + public void feedOutputs() { + boolean value = getOutputValue(); + + for(SettableBoolean output : outputs) output.accept(value); + } +} diff --git a/src/main/java/arachne/lib/pipeline/AbstractDoubleSource.java b/src/main/java/arachne/lib/pipeline/AbstractDoubleSource.java new file mode 100644 index 0000000..80dc481 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/AbstractDoubleSource.java @@ -0,0 +1,40 @@ +package arachne.lib.pipeline; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.SettableDouble; + +public abstract class AbstractDoubleSource implements DoubleSource +{ + protected Set outputs; + + public AbstractDoubleSource() { + this.outputs = new LinkedHashSet(); + } + + protected abstract double getOutputValue(); + + @Override + public SettableT attachOutput(SettableT settable) { + outputs.add(settable); + return settable; + } + + @Override + public boolean detachOutput(SettableDouble settable) { + return outputs.remove(settable); + } + + @Override + public void detachAllOutputs() { + outputs.clear(); + } + + @Override + public void feedOutputs() { + double value = getOutputValue(); + + for(SettableDouble output : outputs) output.accept(value); + } +} diff --git a/src/main/java/arachne/lib/pipeline/AbstractSource.java b/src/main/java/arachne/lib/pipeline/AbstractSource.java new file mode 100644 index 0000000..46834f2 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/AbstractSource.java @@ -0,0 +1,40 @@ +package arachne.lib.pipeline; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.Settable; + +public abstract class AbstractSource implements Source +{ + protected Set> outputs; + + public AbstractSource() { + this.outputs = new LinkedHashSet>(); + } + + protected abstract T getOutputValue(); + + @Override + public > SettableT attachOutput(SettableT settable) { + outputs.add(settable); + return settable; + } + + @Override + public boolean detachOutput(Settable settable) { + return outputs.remove(settable); + } + + @Override + public void detachAllOutputs() { + outputs.clear(); + } + + @Override + public void feedOutputs() { + T value = getOutputValue(); + + for(Settable output : outputs) output.accept(value); + } +} diff --git a/src/main/java/arachne/lib/pipeline/BooleanPipe.java b/src/main/java/arachne/lib/pipeline/BooleanPipe.java new file mode 100644 index 0000000..60b4777 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/BooleanPipe.java @@ -0,0 +1,22 @@ +package arachne.lib.pipeline; + +import arachne.lib.function.BooleanPredicate; +import arachne.lib.io.SettableBoolean; + +public interface BooleanPipe extends BooleanSource, SettableBoolean +{ + void setModifier(BooleanPredicate modifier); + void clearModifier(); + + boolean addFilter(BooleanPredicate predicate); + boolean removeFilter(BooleanPredicate predicate); + void clearFilters(); + + void enableFilteredOutput(boolean defaultValue); + void disableFilteredOutput(); + + default void setFilter(BooleanPredicate predicate) { + clearFilters(); + addFilter(predicate); + } +} diff --git a/src/main/java/arachne/lib/pipeline/BooleanSource.java b/src/main/java/arachne/lib/pipeline/BooleanSource.java new file mode 100644 index 0000000..96ccf04 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/BooleanSource.java @@ -0,0 +1,17 @@ +package arachne.lib.pipeline; + +import arachne.lib.function.BooleanFunction; +import arachne.lib.io.SettableBoolean; + +public interface BooleanSource +{ + SettableT attachOutput(SettableT settable); + boolean detachOutput(SettableBoolean settable); + void detachAllOutputs(); + void feedOutputs(); + + default Source attachTranslator(BooleanFunction translation) { + BooleanTranslator translator = new BooleanTranslator(translation); + return attachOutput(translator); + } +} diff --git a/src/main/java/arachne/lib/pipeline/BooleanTranslator.java b/src/main/java/arachne/lib/pipeline/BooleanTranslator.java new file mode 100644 index 0000000..c46a696 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/BooleanTranslator.java @@ -0,0 +1,25 @@ +package arachne.lib.pipeline; + +import arachne.lib.function.BooleanFunction; +import arachne.lib.io.SettableBoolean; + +public class BooleanTranslator extends AbstractSource implements SettableBoolean +{ + protected R value; + protected final BooleanFunction translation; + + public BooleanTranslator(BooleanFunction translation) { + this.translation = translation; + } + + @Override + protected R getOutputValue() { + return value; + } + + @Override + public void accept(boolean value) { + this.value = translation.apply(value); + feedOutputs(); + } +} diff --git a/src/main/java/arachne/lib/pipeline/DoublePipe.java b/src/main/java/arachne/lib/pipeline/DoublePipe.java new file mode 100644 index 0000000..1f960ef --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/DoublePipe.java @@ -0,0 +1,24 @@ +package arachne.lib.pipeline; + +import java.util.function.DoublePredicate; +import java.util.function.DoubleUnaryOperator; + +import arachne.lib.io.SettableDouble; + +public interface DoublePipe extends DoubleSource, SettableDouble +{ + void setModifier(DoubleUnaryOperator modifier); + void clearModifier(); + + boolean addFilter(DoublePredicate predicate); + boolean removeFilter(DoublePredicate predicate); + void clearFilters(); + + void enableFilteredOutput(double defaultValue); + void disableFilteredOutput(); + + default void setFilter(DoublePredicate predicate) { + clearFilters(); + addFilter(predicate); + } +} diff --git a/src/main/java/arachne/lib/pipeline/DoubleSource.java b/src/main/java/arachne/lib/pipeline/DoubleSource.java new file mode 100644 index 0000000..4f4e8c1 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/DoubleSource.java @@ -0,0 +1,17 @@ +package arachne.lib.pipeline; + +import java.util.function.DoubleFunction; +import arachne.lib.io.SettableDouble; + +public interface DoubleSource +{ + SettableT attachOutput(SettableT settable); + boolean detachOutput(SettableDouble settable); + void detachAllOutputs(); + void feedOutputs(); + + default Source attachTranslator(DoubleFunction translation) { + DoubleTranslator translator = new DoubleTranslator(translation); + return attachOutput(translator); + } +} diff --git a/src/main/java/arachne/lib/pipeline/DoubleTranslator.java b/src/main/java/arachne/lib/pipeline/DoubleTranslator.java new file mode 100644 index 0000000..dfd3a87 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/DoubleTranslator.java @@ -0,0 +1,26 @@ +package arachne.lib.pipeline; + +import java.util.function.DoubleFunction; + +import arachne.lib.io.SettableDouble; + +public class DoubleTranslator extends AbstractSource implements SettableDouble +{ + protected R value; + protected final DoubleFunction translation; + + public DoubleTranslator(DoubleFunction translation) { + this.translation = translation; + } + + @Override + protected R getOutputValue() { + return value; + } + + @Override + public void accept(double value) { + this.value = translation.apply(value); + feedOutputs(); + } +} diff --git a/src/main/java/arachne/lib/pipeline/Pipe.java b/src/main/java/arachne/lib/pipeline/Pipe.java new file mode 100644 index 0000000..dd34c0f --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/Pipe.java @@ -0,0 +1,24 @@ +package arachne.lib.pipeline; + +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +import arachne.lib.io.Settable; + +public interface Pipe extends Source, Settable +{ + void setModifier(UnaryOperator modifier); + void clearModifier(); + + boolean addFilter(Predicate predicate); + boolean removeFilter(Predicate predicate); + void clearFilters(); + + void enableFilteredOutput(T defaultValue); + void disableFilteredOutput(); + + default void setFilter(Predicate predicate) { + clearFilters(); + addFilter(predicate); + } +} diff --git a/src/main/java/arachne/lib/pipeline/SimpleBooleanPipe.java b/src/main/java/arachne/lib/pipeline/SimpleBooleanPipe.java new file mode 100644 index 0000000..18a6cd9 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/SimpleBooleanPipe.java @@ -0,0 +1,81 @@ +package arachne.lib.pipeline; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.function.BooleanPredicate; + +public class SimpleBooleanPipe extends AbstractBooleanSource implements BooleanPipe +{ + protected BooleanPredicate modifier; + protected Set filters; + + protected boolean value; + + protected boolean hasDefaultValue; + protected boolean defaultValue; + + public SimpleBooleanPipe() { + super(); + + this.filters = new LinkedHashSet(); + this.hasDefaultValue = false; + } + + @Override + public void accept(boolean value) { + boolean passesFilters = true; + + for(BooleanPredicate filter : filters) { + if(!filter.test(value)) { + passesFilters = false; + break; + } + } + + this.value = passesFilters ? value : defaultValue; + + if(passesFilters || hasDefaultValue) feedOutputs(); + } + + @Override + protected boolean getOutputValue() { + return modifier != null ? modifier.test(value) : value; + } + + @Override + public void setModifier(BooleanPredicate modifier) { + this.modifier = modifier; + } + + @Override + public void clearModifier() { + this.modifier = null; + } + + @Override + public boolean addFilter(BooleanPredicate predicate) { + return filters.add(predicate); + } + + @Override + public boolean removeFilter(BooleanPredicate predicate) { + return filters.remove(predicate); + } + + @Override + public void clearFilters() { + filters.clear(); + } + + @Override + public void enableFilteredOutput(boolean defaultValue) { + this.hasDefaultValue = true; + this.defaultValue = defaultValue; + } + + @Override + public void disableFilteredOutput() { + this.hasDefaultValue = false; + } +} diff --git a/src/main/java/arachne/lib/pipeline/SimpleBooleanSource.java b/src/main/java/arachne/lib/pipeline/SimpleBooleanSource.java new file mode 100644 index 0000000..60a02ea --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/SimpleBooleanSource.java @@ -0,0 +1,18 @@ +package arachne.lib.pipeline; + +import arachne.lib.io.GettableBoolean; + +public class SimpleBooleanSource extends AbstractBooleanSource +{ + protected final GettableBoolean gettable; + + public SimpleBooleanSource(GettableBoolean gettable) { + super(); + this.gettable = gettable; + } + + @Override + protected boolean getOutputValue() { + return gettable.get(); + } +} diff --git a/src/main/java/arachne/lib/pipeline/SimpleDoublePipe.java b/src/main/java/arachne/lib/pipeline/SimpleDoublePipe.java new file mode 100644 index 0000000..11e544c --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/SimpleDoublePipe.java @@ -0,0 +1,124 @@ +package arachne.lib.pipeline; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.DoublePredicate; +import java.util.function.DoubleUnaryOperator; + +import edu.wpi.first.wpilibj.SpeedController; + +public class SimpleDoublePipe extends AbstractDoubleSource implements DoublePipe +{ + protected DoubleUnaryOperator modifier; + protected Set filters; + + protected double value; + + protected boolean hasDefaultValue; + protected double defaultValue; + + public SimpleDoublePipe() { + super(); + + this.filters = new LinkedHashSet(); + this.hasDefaultValue = false; + } + + @Override + public void accept(double value) { + boolean passesFilters = true; + + for(DoublePredicate filter : filters) { + if(!filter.test(value)) { + passesFilters = false; + break; + } + } + + this.value = passesFilters ? value : defaultValue; + + if(passesFilters || hasDefaultValue) feedOutputs(); + } + + @Override + protected double getOutputValue() { + return modifier != null ? modifier.applyAsDouble(value) : value; + } + + @Override + public void setModifier(DoubleUnaryOperator modifier) { + this.modifier = modifier; + } + + @Override + public void clearModifier() { + this.modifier = null; + } + + @Override + public boolean addFilter(DoublePredicate predicate) { + return filters.add(predicate); + } + + @Override + public boolean removeFilter(DoublePredicate predicate) { + return filters.remove(predicate); + } + + @Override + public void clearFilters() { + filters.clear(); + } + + @Override + public void enableFilteredOutput(double defaultValue) { + this.hasDefaultValue = true; + this.defaultValue = defaultValue; + } + + @Override + public void disableFilteredOutput() { + this.hasDefaultValue = false; + } + + public SpeedController asSpeedController() { + return new SpeedController() { + private boolean isInverted = false; + + @Override + public double get() { + return value; + } + + @Override + public void set(double speed) { + SimpleDoublePipe.this.accept(isInverted ? -speed : speed); + } + + @Override + public void pidWrite(double output) { + SimpleDoublePipe.this.pidWrite(output); + } + + @Override + public boolean getInverted() { + return isInverted; + } + + @Override + public void setInverted(boolean isInverted) { + this.isInverted = isInverted; + } + + @Override + public void disable() { + SimpleDoublePipe.this.accept(0); + } + + @Override + public void stopMotor() { + disable(); + } + }; + } +} diff --git a/src/main/java/arachne/lib/pipeline/SimpleDoubleSource.java b/src/main/java/arachne/lib/pipeline/SimpleDoubleSource.java new file mode 100644 index 0000000..ed2318c --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/SimpleDoubleSource.java @@ -0,0 +1,18 @@ +package arachne.lib.pipeline; + +import arachne.lib.io.GettableDouble; + +public class SimpleDoubleSource extends AbstractDoubleSource +{ + protected final GettableDouble gettable; + + public SimpleDoubleSource(GettableDouble gettable) { + super(); + this.gettable = gettable; + } + + @Override + protected double getOutputValue() { + return gettable.get(); + } +} diff --git a/src/main/java/arachne/lib/pipeline/SimplePipe.java b/src/main/java/arachne/lib/pipeline/SimplePipe.java new file mode 100644 index 0000000..14cad3d --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/SimplePipe.java @@ -0,0 +1,81 @@ +package arachne.lib.pipeline; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Predicate; +import java.util.function.UnaryOperator; + +public class SimplePipe extends AbstractSource implements Pipe +{ + protected UnaryOperator modifier; + protected Set> filters; + + protected T value; + + protected boolean hasDefaultValue; + protected T defaultValue; + + public SimplePipe() { + super(); + + this.filters = new LinkedHashSet>(); + this.hasDefaultValue = false; + } + + @Override + public void accept(T value) { + boolean passesFilters = true; + + for(Predicate filter : filters) { + if(!filter.test(value)) { + passesFilters = false; + break; + } + } + + this.value = passesFilters ? value : defaultValue; + + if(passesFilters || hasDefaultValue) feedOutputs(); + } + + @Override + protected T getOutputValue() { + return modifier != null ? modifier.apply(value) : value; + } + + @Override + public void setModifier(UnaryOperator modifier) { + this.modifier = modifier; + } + + @Override + public void clearModifier() { + this.modifier = null; + } + + @Override + public boolean addFilter(Predicate predicate) { + return filters.add(predicate); + } + + @Override + public boolean removeFilter(Predicate predicate) { + return filters.remove(predicate); + } + + @Override + public void clearFilters() { + filters.clear(); + } + + @Override + public void enableFilteredOutput(T defaultValue) { + this.hasDefaultValue = true; + this.defaultValue = defaultValue; + } + + @Override + public void disableFilteredOutput() { + this.hasDefaultValue = false; + } +} diff --git a/src/main/java/arachne/lib/pipeline/SimpleSource.java b/src/main/java/arachne/lib/pipeline/SimpleSource.java new file mode 100644 index 0000000..e4b4a04 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/SimpleSource.java @@ -0,0 +1,18 @@ +package arachne.lib.pipeline; + +import arachne.lib.io.Gettable; + +public class SimpleSource extends AbstractSource +{ + protected final Gettable gettable; + + public SimpleSource(Gettable gettable) { + super(); + this.gettable = gettable; + } + + @Override + protected T getOutputValue() { + return gettable.get(); + } +} diff --git a/src/main/java/arachne/lib/pipeline/Source.java b/src/main/java/arachne/lib/pipeline/Source.java new file mode 100644 index 0000000..cb81720 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/Source.java @@ -0,0 +1,20 @@ +package arachne.lib.pipeline; + +import java.util.function.Function; + +import arachne.lib.io.Settable; + +public interface Source +{ + > SettableT attachOutput(SettableT settable); + boolean detachOutput(Settable settable); + void detachAllOutputs(); + void feedOutputs(); + + default Source attachTranslator(Function translation) { + Translator translator = new Translator(translation); + attachOutput(translator); + + return translator; + } +} diff --git a/src/main/java/arachne/lib/pipeline/Translator.java b/src/main/java/arachne/lib/pipeline/Translator.java new file mode 100644 index 0000000..15826b5 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/Translator.java @@ -0,0 +1,26 @@ +package arachne.lib.pipeline; + +import java.util.function.Function; + +import arachne.lib.io.Settable; + +public class Translator extends AbstractSource implements Settable +{ + protected R value; + protected final Function translation; + + public Translator(Function translation) { + this.translation = translation; + } + + @Override + protected R getOutputValue() { + return value; + } + + @Override + public void accept(T value) { + this.value = translation.apply(value); + feedOutputs(); + } +} diff --git a/src/main/java/arachne/lib/pipeline/filters/ChangedBooleanFilter.java b/src/main/java/arachne/lib/pipeline/filters/ChangedBooleanFilter.java new file mode 100644 index 0000000..558c22d --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/filters/ChangedBooleanFilter.java @@ -0,0 +1,24 @@ +package arachne.lib.pipeline.filters; + +import arachne.lib.io.GettableBoolean; + +public class ChangedBooleanFilter implements GettableBoolean +{ + protected GettableBoolean gettable; + protected boolean lastValue; + + public ChangedBooleanFilter(GettableBoolean gettable) { + this.gettable = gettable; + this.lastValue = gettable.get(); + } + + @Override + public boolean get() { + boolean value = gettable.get(); + + boolean result = value != lastValue; + this.lastValue = value; + + return result; + } +} diff --git a/src/main/java/arachne/lib/pipeline/filters/ChangedDoubleFilter.java b/src/main/java/arachne/lib/pipeline/filters/ChangedDoubleFilter.java new file mode 100644 index 0000000..94df27d --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/filters/ChangedDoubleFilter.java @@ -0,0 +1,25 @@ +package arachne.lib.pipeline.filters; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.GettableDouble; + +public class ChangedDoubleFilter implements GettableBoolean +{ + protected GettableDouble gettable; + protected double lastValue; + + public ChangedDoubleFilter(GettableDouble gettable) { + this.gettable = gettable; + this.lastValue = gettable.get(); + } + + @Override + public boolean get() { + double value = gettable.get(); + + boolean result = value != lastValue; + this.lastValue = value; + + return result; + } +} diff --git a/src/main/java/arachne/lib/pipeline/filters/ChangedFilter.java b/src/main/java/arachne/lib/pipeline/filters/ChangedFilter.java new file mode 100644 index 0000000..3491a27 --- /dev/null +++ b/src/main/java/arachne/lib/pipeline/filters/ChangedFilter.java @@ -0,0 +1,25 @@ +package arachne.lib.pipeline.filters; + +import arachne.lib.io.Gettable; +import arachne.lib.io.GettableBoolean; + +public class ChangedFilter implements GettableBoolean +{ + protected Gettable gettable; + protected T lastValue; + + public ChangedFilter(Gettable gettable) { + this.gettable = gettable; + this.lastValue = gettable.get(); + } + + @Override + public boolean get() { + T value = gettable.get(); + + boolean result = !value.equals(lastValue); + this.lastValue = value; + + return result; + } +} diff --git a/src/main/java/arachne/lib/requirements/AbstractRequirementManager.java b/src/main/java/arachne/lib/requirements/AbstractRequirementManager.java new file mode 100644 index 0000000..54e4c3d --- /dev/null +++ b/src/main/java/arachne/lib/requirements/AbstractRequirementManager.java @@ -0,0 +1,25 @@ +package arachne.lib.requirements; + +import arachne.lib.logging.ArachneLogger; + +public abstract class AbstractRequirementManager implements RequirementManager +{ + protected UserT currentUser; + + @Override + public boolean isAcquirable() { + return currentUser == null; + } + + @Override + public void acquire(UserT user) { + if(currentUser != null) interrupt(currentUser); + currentUser = user; + } + + @Override + public void release(UserT user) { + if(currentUser == user) currentUser = null; + else ArachneLogger.getInstance().warn("Requirement for type " + user.getClass().getName() + " released, but not owned by releasing object"); + } +} diff --git a/src/main/java/arachne/lib/requirements/RequirementManager.java b/src/main/java/arachne/lib/requirements/RequirementManager.java new file mode 100644 index 0000000..a033397 --- /dev/null +++ b/src/main/java/arachne/lib/requirements/RequirementManager.java @@ -0,0 +1,16 @@ +package arachne.lib.requirements; + +public interface RequirementManager +{ + boolean isAcquirable(); + void acquire(UserT user); + void release(UserT user); + void interrupt(UserT user); + + default boolean request(UserT user) { + if(!isAcquirable()) return false; + + acquire(user); + return true; + } +} diff --git a/src/main/java/arachne/lib/scheduler/Schedulable.java b/src/main/java/arachne/lib/scheduler/Schedulable.java new file mode 100644 index 0000000..446d859 --- /dev/null +++ b/src/main/java/arachne/lib/scheduler/Schedulable.java @@ -0,0 +1,19 @@ +package arachne.lib.scheduler; + +import arachne.lib.sequences.Action; +import arachne.lib.sequences.Actionable; +import arachne.lib.sequences.HostAction; + +@FunctionalInterface +public interface Schedulable extends Runnable, Actionable +{ + @Override + default Action asAction(HostAction host) { + return new Action(host) { + @Override + protected void execute() { + Schedulable.this.run(); + } + }; + } +} diff --git a/src/main/java/arachne/lib/scheduler/ScheduledBooleanSource.java b/src/main/java/arachne/lib/scheduler/ScheduledBooleanSource.java new file mode 100644 index 0000000..cf8469f --- /dev/null +++ b/src/main/java/arachne/lib/scheduler/ScheduledBooleanSource.java @@ -0,0 +1,20 @@ +package arachne.lib.scheduler; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.pipeline.SimpleBooleanSource; +import arachne.lib.pipeline.filters.ChangedBooleanFilter; + +public class ScheduledBooleanSource extends SimpleBooleanSource implements Schedulable +{ + protected GettableBoolean filter; + + public ScheduledBooleanSource(GettableBoolean gettable) { + super(gettable); + + this.filter = new ChangedBooleanFilter(gettable); + } + + public void run() { + if(filter.get()) feedOutputs(); + } +} diff --git a/src/main/java/arachne/lib/scheduler/ScheduledDoubleSource.java b/src/main/java/arachne/lib/scheduler/ScheduledDoubleSource.java new file mode 100644 index 0000000..fc931e8 --- /dev/null +++ b/src/main/java/arachne/lib/scheduler/ScheduledDoubleSource.java @@ -0,0 +1,21 @@ +package arachne.lib.scheduler; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.GettableDouble; +import arachne.lib.pipeline.SimpleDoubleSource; +import arachne.lib.pipeline.filters.ChangedDoubleFilter; + +public class ScheduledDoubleSource extends SimpleDoubleSource implements Schedulable +{ + protected GettableBoolean filter; + + public ScheduledDoubleSource(GettableDouble gettable) { + super(gettable); + + this.filter = new ChangedDoubleFilter(gettable); + } + + public void run() { + if(filter.get()) feedOutputs(); + } +} diff --git a/src/main/java/arachne/lib/scheduler/ScheduledSource.java b/src/main/java/arachne/lib/scheduler/ScheduledSource.java new file mode 100644 index 0000000..dae8847 --- /dev/null +++ b/src/main/java/arachne/lib/scheduler/ScheduledSource.java @@ -0,0 +1,21 @@ +package arachne.lib.scheduler; + +import arachne.lib.io.Gettable; +import arachne.lib.io.GettableBoolean; +import arachne.lib.pipeline.SimpleSource; +import arachne.lib.pipeline.filters.ChangedFilter; + +public class ScheduledSource extends SimpleSource implements Schedulable +{ + protected GettableBoolean filter; + + public ScheduledSource(Gettable gettable) { + super(gettable); + + this.filter = new ChangedFilter(gettable); + } + + public void run() { + if(filter.get()) feedOutputs(); + } +} diff --git a/src/main/java/arachne/lib/sequences/Action.java b/src/main/java/arachne/lib/sequences/Action.java new file mode 100644 index 0000000..b53eb06 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/Action.java @@ -0,0 +1,94 @@ +package arachne.lib.sequences; + +import arachne.lib.logging.ArachneLogger; + +public class Action +{ + protected final HostAction host; + + protected ActionState state; + + public Action(HostAction host) { + this.state = ActionState.PRE_INITIALIZATION; + this.host = host; + } + + protected void initialize() {} + protected void execute() {} + protected void end() {} + + protected void interrupted() { + end(); + } + + protected boolean isFinished() { + return true; + } + + public ActionState act() { + return state.run(this); + } + + public void interrupt() { + interruptSelf(); + interruptHost(); + } + + protected void interruptHost() { + if(host == null) return; + + host.interruptHost(); + host.interruptSelf(); + } + + protected void interruptSelf() { + interrupted(); + state = ActionState.INTERRUPTED; + } + + protected void handleInterruptFromHost() { + interruptSelf(); + } + + public enum ActionState { + PRE_INITIALIZATION { + @Override + protected ActionState run(Action action) { + action.initialize(); + action.state = RUNNING; + return RUNNING.run(action); + } + }, + RUNNING { + @Override + protected ActionState run(Action action) { + action.execute(); + + if(action.state == INTERRUPTED) return INTERRUPTED.run(action); + + if(action.isFinished()) { + action.end(); + action.state = FINISHED; + } + + return action.state; + } + }, + FINISHED { + @Override + protected ActionState run(Action action) { + ArachneLogger.getInstance().error("Tried to run an action that has already finished! Doing nothing."); + return FINISHED; + } + }, + INTERRUPTED { + @Override + protected ActionState run(Action action) { + action.state = FINISHED; + return INTERRUPTED; + } + }; + + protected abstract ActionState run(Action action); + } +} diff --git a/src/main/java/arachne/lib/sequences/ActionConductor.java b/src/main/java/arachne/lib/sequences/ActionConductor.java new file mode 100644 index 0000000..ef4542c --- /dev/null +++ b/src/main/java/arachne/lib/sequences/ActionConductor.java @@ -0,0 +1,49 @@ +package arachne.lib.sequences; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.scheduler.Schedulable; +import arachne.lib.sequences.Action.ActionState; + +public class ActionConductor implements Schedulable +{ + protected Set actions; + + public ActionConductor() { + this.actions = new LinkedHashSet(); + } + + @Override + public void run() { + actions.removeIf((action) -> { + ActionState state = action.act(); + return state == ActionState.INTERRUPTED || state == ActionState.FINISHED; + }); + } + + public Action add(Action action) { + actions.add(action); + return action; + } + + public Action add(Actionable actionable) { + return add(actionable, null); + } + + public Action add(Actionable actionable, HostAction host) { + return add(actionable.asAction(host)); + } + + public void clear() { + actions.clear(); + } + + public boolean hasActions() { + return !actions.isEmpty(); + } + + public void interrupt() { + for(Action action : actions) action.interrupt(); + } +} diff --git a/src/main/java/arachne/lib/sequences/ActionWrapper.java b/src/main/java/arachne/lib/sequences/ActionWrapper.java new file mode 100644 index 0000000..e2288ad --- /dev/null +++ b/src/main/java/arachne/lib/sequences/ActionWrapper.java @@ -0,0 +1,44 @@ +package arachne.lib.sequences; + +public abstract class ActionWrapper extends HostAction +{ + protected Action currentAction; + + protected abstract Action getNextAction(boolean isFirst); + + public ActionWrapper(HostAction host) { + super(host); + } + + @Override + protected void initialize() { + currentAction = getNextAction(true); + } + + @Override + protected void execute() { + ActionState subState = null; + + while(currentAction != null && (subState = currentAction.act()) == ActionState.FINISHED) { + currentAction = getNextAction(false); + } + + if(subState == ActionState.INTERRUPTED) state = ActionState.INTERRUPTED; + } + + @Override + protected boolean isFinished() { + return currentAction == null; + } + + @Override + protected void interruptChildren() { + if(currentAction != null) currentAction.handleInterruptFromHost(); + } + + @Override + protected void handleInterruptFromChild(Action interruptSource) { + interruptSelf(); + interruptHost(); + } +} diff --git a/src/main/java/arachne/lib/sequences/Actionable.java b/src/main/java/arachne/lib/sequences/Actionable.java new file mode 100644 index 0000000..a5e28a0 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/Actionable.java @@ -0,0 +1,7 @@ +package arachne.lib.sequences; + +@FunctionalInterface +public interface Actionable +{ + Action asAction(HostAction host); +} diff --git a/src/main/java/arachne/lib/sequences/BinarySelector.java b/src/main/java/arachne/lib/sequences/BinarySelector.java new file mode 100644 index 0000000..4eafab4 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/BinarySelector.java @@ -0,0 +1,68 @@ +package arachne.lib.sequences; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +import arachne.lib.io.GettableBoolean; + +public class BinarySelector implements Actionable +{ + protected final List options; + protected Actionable actionableAsElse; + + public BinarySelector() { + this(null); + } + + public BinarySelector(Actionable actionableAsElse, BinaryOption... options) { + this.actionableAsElse = actionableAsElse; + this.options = new ArrayList(Arrays.asList(options)); + } + + public BinarySelector addOption(GettableBoolean condition, Actionable actionable) { + options.add(new BinaryOption(condition, actionable)); + return this; + } + + public BinarySelector setElse(Actionable actionable) { + this.actionableAsElse = actionable; + return this; + } + + @Override + public Action asAction(HostAction host) { + return new ActionWrapper(host) { + @Override + protected Action getNextAction(boolean isFirst) { + if(!isFirst) return null; + + for(BinaryOption option : options) { + if(option.get()) return option.asAction(this); + } + + return actionableAsElse != null ? actionableAsElse.asAction(this) : null; + } + }; + } + + public static class BinaryOption implements Actionable, GettableBoolean { + protected final GettableBoolean condition; + protected final Actionable actionable; + + public BinaryOption(GettableBoolean condition, Actionable actionable) { + this.condition = condition; + this.actionable = actionable; + } + + @Override + public boolean get() { + return condition.get(); + } + + @Override + public Action asAction(HostAction host) { + return actionable.asAction(host); + } + } +} diff --git a/src/main/java/arachne/lib/sequences/Binding.java b/src/main/java/arachne/lib/sequences/Binding.java new file mode 100644 index 0000000..56eec3e --- /dev/null +++ b/src/main/java/arachne/lib/sequences/Binding.java @@ -0,0 +1,32 @@ +package arachne.lib.sequences; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.Settable; +import arachne.lib.pipeline.Source; +import arachne.lib.scheduler.Schedulable; + +public class Binding implements Actionable +{ + protected Source source; + protected Set> outputs; + protected Schedulable feedMethod; + protected GettableBoolean endingCondition; + + @SafeVarargs + public Binding(Source source, Schedulable feedMethod, GettableBoolean endingCondition, Settable... outputs) { + this.source = source; + this.feedMethod = feedMethod; + this.endingCondition = endingCondition; + + this.outputs = new LinkedHashSet>(); + for(Settable output : outputs) this.outputs.add(output); + } + + @Override + public Action asAction(HostAction host) { + return new BindingAction(host, source, outputs, feedMethod, endingCondition); + } +} diff --git a/src/main/java/arachne/lib/sequences/BindingAction.java b/src/main/java/arachne/lib/sequences/BindingAction.java new file mode 100644 index 0000000..533d6bc --- /dev/null +++ b/src/main/java/arachne/lib/sequences/BindingAction.java @@ -0,0 +1,45 @@ +package arachne.lib.sequences; + +import java.util.Set; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.Settable; +import arachne.lib.pipeline.Source; +import arachne.lib.scheduler.Schedulable; + +public class BindingAction extends Action +{ + protected Source source; + protected Set> outputs; + protected Schedulable feedMethod; + protected GettableBoolean endingCondition; + + public BindingAction(HostAction host, Source source, Set> outputs, Schedulable feedMethod, GettableBoolean endingCondition) { + super(host); + + this.source = source; + this.outputs = outputs; + this.feedMethod = feedMethod; + this.endingCondition = endingCondition; + } + + @Override + protected void initialize() { + for(Settable output : outputs) source.attachOutput(output); + } + + @Override + protected void execute() { + if(feedMethod != null) feedMethod.run(); + } + + @Override + protected void end() { + for(Settable output : outputs) source.detachOutput(output); + } + + @Override + protected boolean isFinished() { + return endingCondition.get(); + } +} diff --git a/src/main/java/arachne/lib/sequences/BooleanBinding.java b/src/main/java/arachne/lib/sequences/BooleanBinding.java new file mode 100644 index 0000000..fe2aad3 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/BooleanBinding.java @@ -0,0 +1,31 @@ +package arachne.lib.sequences; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.SettableBoolean; +import arachne.lib.pipeline.BooleanSource; +import arachne.lib.scheduler.Schedulable; + +public class BooleanBinding implements Actionable +{ + protected BooleanSource source; + protected Set outputs; + protected Schedulable feedMethod; + protected GettableBoolean endingCondition; + + public BooleanBinding(BooleanSource source, Schedulable feedMethod, GettableBoolean endingCondition, SettableBoolean... outputs) { + this.source = source; + this.feedMethod = feedMethod; + this.endingCondition = endingCondition; + + this.outputs = new LinkedHashSet(); + for(SettableBoolean output : outputs) this.outputs.add(output); + } + + @Override + public Action asAction(HostAction host) { + return new BooleanBindingAction(host, source, outputs, feedMethod, endingCondition); + } +} diff --git a/src/main/java/arachne/lib/sequences/BooleanBindingAction.java b/src/main/java/arachne/lib/sequences/BooleanBindingAction.java new file mode 100644 index 0000000..289c372 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/BooleanBindingAction.java @@ -0,0 +1,45 @@ +package arachne.lib.sequences; + +import java.util.Set; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.SettableBoolean; +import arachne.lib.pipeline.BooleanSource; +import arachne.lib.scheduler.Schedulable; + +public class BooleanBindingAction extends Action +{ + protected BooleanSource source; + protected Set outputs; + protected Schedulable feedMethod; + protected GettableBoolean endingCondition; + + public BooleanBindingAction(HostAction host, BooleanSource source, Set outputs, Schedulable feedMethod, GettableBoolean endingCondition) { + super(host); + + this.source = source; + this.outputs = outputs; + this.feedMethod = feedMethod; + this.endingCondition = endingCondition; + } + + @Override + protected void initialize() { + for(SettableBoolean output : outputs) source.attachOutput(output); + } + + @Override + protected void execute() { + if(feedMethod != null) feedMethod.run(); + } + + @Override + protected void end() { + for(SettableBoolean output : outputs) source.detachOutput(output); + } + + @Override + protected boolean isFinished() { + return endingCondition.get(); + } +} diff --git a/src/main/java/arachne/lib/sequences/Clock.java b/src/main/java/arachne/lib/sequences/Clock.java new file mode 100644 index 0000000..864df30 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/Clock.java @@ -0,0 +1,22 @@ +package arachne.lib.sequences; + +import arachne.lib.io.GettableBoolean; + +public class Clock +{ + public static GettableBoolean delay(long milliseconds) { + return new RepeatableCondition() { + long startTime; + + @Override + protected void initialize() { + startTime = System.currentTimeMillis(); + } + + @Override + protected boolean condition() { + return startTime + milliseconds <= System.currentTimeMillis(); + } + }; + } +} diff --git a/src/main/java/arachne/lib/sequences/DoubleBinding.java b/src/main/java/arachne/lib/sequences/DoubleBinding.java new file mode 100644 index 0000000..980868c --- /dev/null +++ b/src/main/java/arachne/lib/sequences/DoubleBinding.java @@ -0,0 +1,31 @@ +package arachne.lib.sequences; + +import java.util.LinkedHashSet; +import java.util.Set; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.SettableDouble; +import arachne.lib.pipeline.DoubleSource; +import arachne.lib.scheduler.Schedulable; + +public class DoubleBinding implements Actionable +{ + protected DoubleSource source; + protected Set outputs; + protected Schedulable feedMethod; + protected GettableBoolean endingCondition; + + public DoubleBinding(DoubleSource source, Schedulable feedMethod, GettableBoolean endingCondition, SettableDouble... outputs) { + this.source = source; + this.feedMethod = feedMethod; + this.endingCondition = endingCondition; + + this.outputs = new LinkedHashSet(); + for(SettableDouble output : outputs) this.outputs.add(output); + } + + @Override + public Action asAction(HostAction host) { + return new DoubleBindingAction(host, source, outputs, feedMethod, endingCondition); + } +} diff --git a/src/main/java/arachne/lib/sequences/DoubleBindingAction.java b/src/main/java/arachne/lib/sequences/DoubleBindingAction.java new file mode 100644 index 0000000..d24ca03 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/DoubleBindingAction.java @@ -0,0 +1,45 @@ +package arachne.lib.sequences; + +import java.util.Set; + +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.SettableDouble; +import arachne.lib.pipeline.DoubleSource; +import arachne.lib.scheduler.Schedulable; + +public class DoubleBindingAction extends Action +{ + protected DoubleSource source; + protected Set outputs; + protected Schedulable feedMethod; + protected GettableBoolean endingCondition; + + public DoubleBindingAction(HostAction host, DoubleSource source, Set outputs, Schedulable feedMethod, GettableBoolean endingCondition) { + super(host); + + this.source = source; + this.outputs = outputs; + this.feedMethod = feedMethod; + this.endingCondition = endingCondition; + } + + @Override + protected void initialize() { + for(SettableDouble output : outputs) source.attachOutput(output); + } + + @Override + protected void execute() { + if(feedMethod != null) feedMethod.run(); + } + + @Override + protected void end() { + for(SettableDouble output : outputs) source.detachOutput(output); + } + + @Override + protected boolean isFinished() { + return endingCondition.get(); + } +} diff --git a/src/main/java/arachne/lib/sequences/Fork.java b/src/main/java/arachne/lib/sequences/Fork.java new file mode 100644 index 0000000..124dc14 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/Fork.java @@ -0,0 +1,59 @@ +package arachne.lib.sequences; + +import java.util.Arrays; +import java.util.Collection; +import java.util.LinkedHashSet; +import java.util.Set; + +public class Fork implements Actionable +{ + protected final Set actionables; + + public Fork(Actionable... actionables) { + this.actionables = new LinkedHashSet(Arrays.asList(actionables)); + } + + public void add(Actionable actionable) { + actionables.add(actionable); + } + + public void clear() { + actionables.clear(); + } + + @Override + public Action asAction(HostAction host) { + return new ForkAction(host, actionables); + } + + protected class ForkAction extends HostAction { + protected final ActionConductor scheduler; + + protected boolean isInterruptScheduled = false; + + protected ForkAction(HostAction host, Collection actionables) { + super(host); + + this.scheduler = new ActionConductor(); + for(Actionable actionable : actionables) scheduler.add(actionable, this); + } + + @Override + protected void execute() { + scheduler.run(); + } + + @Override + protected boolean isFinished() { + return !scheduler.hasActions(); + } + + @Override + protected void interruptChildren() { + scheduler.interrupt(); + } + + @Override + protected void handleInterruptFromChild(Action interruptSource) { /* Do nothing */ } + } +} diff --git a/src/main/java/arachne/lib/sequences/HostAction.java b/src/main/java/arachne/lib/sequences/HostAction.java new file mode 100644 index 0000000..2111a27 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/HostAction.java @@ -0,0 +1,23 @@ +package arachne.lib.sequences; + +public abstract class HostAction extends Action +{ + public HostAction(HostAction host) { + super(host); + } + + @Override + public void interrupt() { + interruptChildren(); + super.interrupt(); + } + + @Override + protected void handleInterruptFromHost() { + interruptChildren(); + super.handleInterruptFromHost(); + } + + protected abstract void interruptChildren(); + protected abstract void handleInterruptFromChild(Action interruptSource); +} diff --git a/src/main/java/arachne/lib/sequences/RepeatableCondition.java b/src/main/java/arachne/lib/sequences/RepeatableCondition.java new file mode 100644 index 0000000..ed670d9 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/RepeatableCondition.java @@ -0,0 +1,40 @@ +package arachne.lib.sequences; + +import arachne.lib.io.GettableBoolean; + +public abstract class RepeatableCondition implements GettableBoolean +{ + protected boolean shouldRepeat, isInitialized; + + public RepeatableCondition() { + this(true); + } + + public RepeatableCondition(boolean shouldRepeat) { + this.shouldRepeat = shouldRepeat; + this.isInitialized = false; + } + + @Override + public boolean get() { + if(!isInitialized) { + initialize(); + isInitialized = true; + } + + if(condition()) { + if(shouldRepeat) { + reset(); + isInitialized = false; + } + return true; + } + + return false; + } + + protected void initialize() {} + protected void reset() {} + + protected abstract boolean condition(); +} diff --git a/src/main/java/arachne/lib/sequences/Sequence.java b/src/main/java/arachne/lib/sequences/Sequence.java new file mode 100644 index 0000000..f5f21a4 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/Sequence.java @@ -0,0 +1,30 @@ +package arachne.lib.sequences; + +import java.util.ArrayList; +import java.util.Arrays; +import java.util.List; + +public class Sequence implements Actionable +{ + protected List actionables; + + public Sequence(Actionable... actionables) { + this.actionables = new ArrayList(Arrays.asList(actionables)); + } + + @Override + public Action asAction(HostAction host) { + return new ActionWrapper(host) { + int counter; + + @Override + protected Action getNextAction(boolean isFirst) { + if(isFirst) counter = 0; + else counter++; + + if(counter < Sequence.this.actionables.size()) return Sequence.this.actionables.get(counter).asAction(this); + return null; + } + }; + } +} diff --git a/src/main/java/arachne/lib/sequences/SimpleSelector.java b/src/main/java/arachne/lib/sequences/SimpleSelector.java new file mode 100644 index 0000000..275f108 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/SimpleSelector.java @@ -0,0 +1,33 @@ +package arachne.lib.sequences; + +import arachne.lib.io.GettableBoolean; + +public class SimpleSelector implements Actionable +{ + protected final GettableBoolean condition; + protected final Actionable actionableOnTrue; + protected final Actionable actionableOnFalse; + + public SimpleSelector(GettableBoolean condition, Actionable actionableOnTrue) { + this(condition, actionableOnTrue, null); + } + + public SimpleSelector(GettableBoolean condition, Actionable actionableOnTrue, Actionable actionableOnFalse) { + this.actionableOnTrue = actionableOnTrue; + this.actionableOnFalse = actionableOnFalse; + this.condition = condition; + } + + @Override + public Action asAction(HostAction host) { + return new ActionWrapper(host) { + @Override + protected Action getNextAction(boolean isFirst) { + if(!isFirst) return null; + + Actionable actionable = SimpleSelector.this.condition.get() ? SimpleSelector.this.actionableOnTrue : SimpleSelector.this.actionableOnFalse; + return actionable != null ? actionable.asAction(this) : null; + } + }; + } +} diff --git a/src/main/java/arachne/lib/sequences/WithRequirement.java b/src/main/java/arachne/lib/sequences/WithRequirement.java new file mode 100644 index 0000000..63d93b1 --- /dev/null +++ b/src/main/java/arachne/lib/sequences/WithRequirement.java @@ -0,0 +1,42 @@ +package arachne.lib.sequences; + +import java.util.HashSet; +import java.util.Set; + +import arachne.lib.requirements.RequirementManager; + +public class WithRequirement implements Actionable +{ + protected final Actionable actionable; + protected Set> requirements; + + @SafeVarargs + public WithRequirement(Actionable actionable, RequirementManager... requirements) { + this.actionable = actionable; + + this.requirements = new HashSet>(); + for(RequirementManager requirement : requirements) this.requirements.add(requirement); + } + + @Override + public Action asAction(HostAction host) { + return new ActionWrapper(host) { + @Override + protected void initialize() { + super.initialize(); + for(RequirementManager requirement : WithRequirement.this.requirements) requirement.acquire(this); + } + + @Override + protected void end() { + super.end(); + for(RequirementManager requirement : WithRequirement.this.requirements) requirement.release(this); + } + + @Override + protected Action getNextAction(boolean isFirst) { + return isFirst ? actionable.asAction(this) : null; + } + }; + } +} diff --git a/src/main/java/arachne/lib/states/State.java b/src/main/java/arachne/lib/states/State.java new file mode 100644 index 0000000..023e853 --- /dev/null +++ b/src/main/java/arachne/lib/states/State.java @@ -0,0 +1,16 @@ +package arachne.lib.states; + +public interface State< + StateT extends Enum & State, + SubsystemT extends StatefulSubsystem +> +{ + default StateT getTransitionalStateFor(StateT nextState, SubsystemT subsystem) { + return nextState; + } + + default void constructState(SubsystemT subsystem) {} + default void deconstructState(SubsystemT subsystem) {} + + default void run(SubsystemT subsystem) {} +} diff --git a/src/main/java/arachne/lib/states/StatefulSubsystem.java b/src/main/java/arachne/lib/states/StatefulSubsystem.java new file mode 100644 index 0000000..f8cd904 --- /dev/null +++ b/src/main/java/arachne/lib/states/StatefulSubsystem.java @@ -0,0 +1,77 @@ +package arachne.lib.states; + +import java.util.HashMap; +import java.util.LinkedHashSet; +import java.util.Map; +import java.util.Set; + +import arachne.lib.scheduler.Schedulable; +import arachne.lib.systems.Subsystem; + +public abstract class StatefulSubsystem< + StateT extends Enum & State, + SubsystemT extends StatefulSubsystem +> extends Subsystem +{ + protected StateT state; + + protected Set currentStateBindings; + protected Map> stateBindingMap; + + protected StatefulSubsystem(StateT initialState, Class enumClass) { + this.state = initialState; + this.stateBindingMap = new HashMap>(); + + for(StateT state : enumClass.getEnumConstants()) { + stateBindingMap.put(state, new LinkedHashSet()); + } + } + + @Override + protected void initialize() { + state.constructState(getSelf()); + currentStateBindings = stateBindingMap.get(this.state); + _initialize(); + } + + protected abstract void _initialize(); + + @Override + public void run() { + super.run(); + state.run(getSelf()); + } + + @Override + public void runBindings() { + super.runBindings(); + + if(currentStateBindings != null) { + for(Schedulable binding : currentStateBindings) binding.run(); + } + } + + protected abstract SubsystemT getSelf(); + + public StateT getState() { + return state; + } + + public void setState(StateT state) { + this.state.deconstructState(getSelf()); + this.state = this.state.getTransitionalStateFor(state, getSelf()); + currentStateBindings = stateBindingMap.get(this.state); + this.state.constructState(getSelf()); + } + + public BindingT addBinding(StateT state, BindingT binding) { + stateBindingMap.get(state).add(binding); + return binding; + } + + public void removeBinding(Schedulable binding) { + super.removeBinding(binding); + + for(Set stateBindings : stateBindingMap.values()) stateBindings.remove(binding); + } +} diff --git a/src/main/java/arachne/lib/systems/Subsystem.java b/src/main/java/arachne/lib/systems/Subsystem.java new file mode 100644 index 0000000..bbf9f0d --- /dev/null +++ b/src/main/java/arachne/lib/systems/Subsystem.java @@ -0,0 +1,131 @@ +package arachne.lib.systems; + +import java.util.LinkedHashSet; +import java.util.Set; +import java.util.function.Supplier; + +import arachne.lib.requirements.AbstractRequirementManager; +import arachne.lib.scheduler.Schedulable; +import arachne.lib.sequences.Action; +import arachne.lib.sequences.ActionConductor; + +public abstract class Subsystem extends AbstractRequirementManager implements Schedulable, Tree +{ + protected Set bindings; + protected ActionConductor internalActionScheduler; + + protected Subsystem parent; + protected Set children; + + public Subsystem() { + this.bindings = new LinkedHashSet(); + this.internalActionScheduler = addBinding(new ActionConductor()); + + this.children = new LinkedHashSet(); + } + + // -------------------- + // Factory methods + // -------------------- + + public static T create(Supplier subsystemSupplier) { + return create(subsystemSupplier, null); + } + + public static T create(Supplier subsystemSupplier, Subsystem parent) { + T subsystem = subsystemSupplier.get(); + subsystem.setParent(parent); + subsystem.initialize(); + + return subsystem; + } + + protected abstract void initialize(); + + // -------------------- + // Schedulable methods + // -------------------- + + @Override + public void run() { + runBindings(); + _run(); + } + + protected void runBindings() { + for(Schedulable binding : bindings) binding.run(); + } + + protected void _run() {} + + public BindingT addBinding(BindingT binding) { + bindings.add(binding); + return binding; + } + + public void removeBinding(Schedulable binding) { + bindings.remove(binding); + } + + public void interruptInternalActions() { + internalActionScheduler.interrupt(); + } + + // -------------------- + // Tree methods + // -------------------- + + @Override + public final Subsystem getSelfAsTree() { + return this; + } + + @Override + public Subsystem getParent() { + return parent; + } + + @Override + public Set getChildren() { + return children; + } + + @Override + public void setParent(Subsystem parent) { + if(this.parent != null) this.parent.removeChild(this); + + this.parent = parent; + + if(this.parent != null) this.parent.addChild(this); + } + + protected void addChild(Subsystem child) { + children.add(child); + } + + protected void removeChild(Subsystem child) { + children.remove(child); + } + + // -------------------- + // AbstractRequirementManager methods + // -------------------- + + @Override + public boolean isAcquirable() { + return !search(OperationFilter.FAMILY_LINE, Subsystem::isBusy); + } + + protected boolean isBusy() { + return currentUser != null; + } + + @Override + public void interrupt(Action user) { + apply(OperationFilter.FAMILY_LINE, Subsystem::_interrupt); + } + + protected void _interrupt() { + if(currentUser != null) currentUser.interrupt(); + } +} diff --git a/src/main/java/arachne/lib/systems/Tree.java b/src/main/java/arachne/lib/systems/Tree.java new file mode 100644 index 0000000..82eac6a --- /dev/null +++ b/src/main/java/arachne/lib/systems/Tree.java @@ -0,0 +1,96 @@ +package arachne.lib.systems; + +import java.util.Set; +import java.util.function.Consumer; +import java.util.function.Predicate; +import static arachne.lib.systems.Tree.OperationFilter.*; + +public interface Tree> +{ + void setParent(TreeT parent); + + TreeT getSelfAsTree(); + TreeT getParent(); + Set getChildren(); + + public static class OperationFilter { + public static final int + ALL = 0, + THIS = 1, + ANCESTORS = 1 << 1, + DESCENDANTS = 1 << 2; + + public static final int + FAMILY_LINE = THIS | ANCESTORS | DESCENDANTS; + } + + default boolean search(int nodesToTraverse, Predicate condition) { + return search(getSelfAsTree(), nodesToTraverse, condition); + } + + private static > boolean search(TreeT initialNode, int nodesToTraverse, Predicate condition) { + if(initialNode == null) return false; + + // If there are no filters, search the whole tree + if(nodesToTraverse == ALL) { + TreeT parent = initialNode.getParent(); + + // If current node has a parent, elevate the starting point to the parent + if(parent != null) return search(parent, ALL, condition); + + // If the current node is the top of the tree, do a depth first search + return search(initialNode, THIS | DESCENDANTS, condition); + } + + // Test each filter + if((nodesToTraverse & THIS) > 0) { + if(condition.test(initialNode)) return true; + } + + if((nodesToTraverse & ANCESTORS) > 0) { + if(search(initialNode.getParent(), THIS | ANCESTORS, condition)) return true; + } + + if((nodesToTraverse & DESCENDANTS) > 0) { + for(TreeT child : initialNode.getChildren()) { + if(search(child, THIS | DESCENDANTS, condition)) return true; + } + } + + return false; + } + + default void apply(int nodesToTraverse, Consumer operation) { + apply(getSelfAsTree(), nodesToTraverse, operation); + } + + private static > void apply(TreeT initialNode, int nodesToTraverse, Consumer operation) { + if(initialNode == null) return; + + // If there are no filters, search the whole tree + if(nodesToTraverse == ALL) { + TreeT parent = initialNode.getParent(); + + // If current node has a parent, elevate the starting point to the parent + if(parent != null) apply(parent, ALL, operation); + + // If the current node is the top of the tree, do a depth first search + else apply(initialNode, THIS | DESCENDANTS, operation); + } + + // Test each filter + if((nodesToTraverse & THIS) > 0) { + operation.accept(initialNode); + } + + if((nodesToTraverse & ANCESTORS) > 0) { + apply(initialNode.getParent(), THIS | ANCESTORS, operation); + } + + if((nodesToTraverse & DESCENDANTS) > 0) { + for(TreeT child : initialNode.getChildren()) { + apply(child, THIS | DESCENDANTS, operation); + } + } + } +} diff --git a/src/main/java/arachne/tapestry/SequencesTapestry.java b/src/main/java/arachne/tapestry/SequencesTapestry.java new file mode 100644 index 0000000..fa3a03c --- /dev/null +++ b/src/main/java/arachne/tapestry/SequencesTapestry.java @@ -0,0 +1,307 @@ +package arachne.tapestry; + +import java.util.function.Function; + +import arachne.lib.io.Gettable; +import arachne.lib.io.GettableBoolean; +import arachne.lib.io.GettableDouble; +import arachne.lib.io.Settable; +import arachne.lib.io.SettableBoolean; +import arachne.lib.io.SettableDouble; +import arachne.lib.pipeline.BooleanSource; +import arachne.lib.pipeline.DoubleSource; +import arachne.lib.pipeline.Source; +import arachne.lib.requirements.RequirementManager; +import arachne.lib.scheduler.Schedulable; +import arachne.lib.scheduler.ScheduledBooleanSource; +import arachne.lib.scheduler.ScheduledDoubleSource; +import arachne.lib.scheduler.ScheduledSource; +import arachne.lib.sequences.Action; +import arachne.lib.sequences.Actionable; +import arachne.lib.sequences.BinarySelector; +import arachne.lib.sequences.Binding; +import arachne.lib.sequences.BooleanBinding; +import arachne.lib.sequences.DoubleBinding; +import arachne.lib.sequences.Fork; +import arachne.lib.sequences.HostAction; +import arachne.lib.sequences.Sequence; +import arachne.lib.sequences.WithRequirement; + +public class SequencesTapestry +{ + // -------------------- + // Basic actionables + // -------------------- + + public static Actionable DO_NOTHING() { + return Action::new; + } + + public static Actionable DO(Schedulable schedulable) { + return schedulable; + } + + public static Actionable WAIT_UNTIL(GettableBoolean condition) { + return condition; + } + + public static Actionable SEQUENCE(Actionable... actionables) { + return new Sequence(actionables); + } + + // -------------------- + // Multi-actions + // -------------------- + + public static Split SPLIT(Actionable actionable) { + return new Split(actionable); + } + + public static final class Split implements Actionable { + protected final Fork fork; + + protected Split(Actionable actionable) { + this.fork = new Fork(actionable); + } + + public final Split AND(Actionable actionable) { + fork.add(actionable); + return this; + } + + @Override + public final Action asAction(HostAction host) { + return fork.asAction(host); + } + } + + // -------------------- + // Repetition + // -------------------- + + public static Repeat REPEAT(Schedulable schedulable) { + return new Repeat(schedulable); + } + + public static final class Repeat { + protected final Schedulable schedulable; + + protected Repeat(Schedulable schedulable) { + this.schedulable = schedulable; + } + + public final Actionable UNTIL(GettableBoolean condition) { + return (host) -> new Action(host) { + @Override + protected void execute() { + Repeat.this.schedulable.run(); + } + + @Override + protected boolean isFinished() { + return condition.get(); + } + }; + } + } + + // -------------------- + // Selection + // -------------------- + + public static If IF(GettableBoolean condition) { + return new If(condition); + } + + public static final class If { + protected final BinarySelector selector; + protected final GettableBoolean condition; + + protected If(GettableBoolean condition) { + this(new BinarySelector(), condition); + } + + protected If(BinarySelector selector, GettableBoolean condition) { + this.selector = selector; + this.condition = condition; + } + + public final IfThen THEN(Actionable actionable) { + return new IfThen(selector.addOption(condition, actionable)); + } + } + + public static final class IfThen implements Actionable { + protected final BinarySelector selector; + + protected IfThen(BinarySelector selector) { + this.selector = selector; + } + + @Override + public final Action asAction(HostAction host) { + return selector.asAction(host); + } + + public final If ELSE_IF(GettableBoolean condition) { + return new If(selector, condition); + } + + public final Actionable ELSE(Actionable actionable) { + return selector.setElse(actionable); + } + } + + // -------------------- + // Requirements and dependencies + // -------------------- + + @SafeVarargs + public static Require REQUIRE(RequirementManager... requirements) { + return new Require(requirements); + } + + public static final class Require { + protected final RequirementManager[] requirements; + + protected Require(RequirementManager[] requirements) { + this.requirements = requirements; + } + + public final Actionable FOR(Actionable actionable) { + return new WithRequirement(actionable, requirements); + } + } + + // -------------------- + // Temporary bindings + // -------------------- + + public static Bind FEED(Gettable gettable) { + return BIND(new ScheduledSource(gettable), (source) -> source); + } + + public static BindBoolean FEED(GettableBoolean gettable) { + return BIND(new ScheduledBooleanSource(gettable), (source) -> source); + } + + public static BindDouble FEED(GettableDouble gettable) { + return BIND(new ScheduledDoubleSource(gettable), (source) -> source); + } + + public static Bind BIND(Source source) { + return new Bind(source, null); + } + + public static BindBoolean BIND(BooleanSource source) { + return new BindBoolean(source, null); + } + + public static BindDouble BIND(DoubleSource source) { + return new BindDouble(source, null); + } + + public static > Bind BIND(SourceT source, Function feedMethodGenerator) { + return new Bind(source, feedMethodGenerator.apply(source)); + } + + public static BindBoolean BIND(SourceT source, Function feedMethodGenerator) { + return new BindBoolean(source, feedMethodGenerator.apply(source)); + } + + public static BindDouble BIND(SourceT source, Function feedMethodGenerator) { + return new BindDouble(source, feedMethodGenerator.apply(source)); + } + + public static final class Bind { + protected final Source source; + protected final Schedulable feedMethod; + + protected Bind(Source source, Schedulable feedMethod) { + this.source = source; + this.feedMethod = feedMethod; + } + + @SafeVarargs + public final BindTo TO(Settable... outputs) { + return new BindTo(source, feedMethod, outputs); + } + } + + public static final class BindBoolean { + protected final BooleanSource source; + protected final Schedulable feedMethod; + + protected BindBoolean(BooleanSource source, Schedulable feedMethod) { + this.source = source; + this.feedMethod = feedMethod; + } + + public final BindBooleanTo TO(SettableBoolean... outputs) { + return new BindBooleanTo(source, feedMethod, outputs); + } + } + + public static final class BindDouble { + protected final DoubleSource source; + protected final Schedulable feedMethod; + + protected BindDouble(DoubleSource source, Schedulable feedMethod) { + this.source = source; + this.feedMethod = feedMethod; + } + + public final BindDoubleTo TO(SettableDouble... outputs) { + return new BindDoubleTo(source, feedMethod, outputs); + } + } + + public static final class BindTo { + protected final Source source; + protected final Schedulable feedMethod; + protected final Settable[] outputs; + + @SafeVarargs + protected BindTo(Source source, Schedulable feedMethod, Settable... outputs) { + this.source = source; + this.feedMethod = feedMethod; + this.outputs = outputs; + } + + public final Actionable UNTIL(GettableBoolean condition) { + return new Binding(source, feedMethod, condition, outputs); + } + } + + public static final class BindBooleanTo { + protected final BooleanSource source; + protected final Schedulable feedMethod; + protected final SettableBoolean[] outputs; + + protected BindBooleanTo(BooleanSource source, Schedulable feedMethod, SettableBoolean... outputs) { + this.source = source; + this.feedMethod = feedMethod; + this.outputs = outputs; + } + + public final Actionable UNTIL(GettableBoolean condition) { + return new BooleanBinding(source, feedMethod, condition, outputs); + } + } + + public static final class BindDoubleTo { + protected final DoubleSource source; + protected final Schedulable feedMethod; + protected final SettableDouble[] outputs; + + protected BindDoubleTo(DoubleSource source, Schedulable feedMethod, SettableDouble... outputs) { + this.source = source; + this.feedMethod = feedMethod; + this.outputs = outputs; + } + + public final Actionable UNTIL(GettableBoolean condition) { + return new DoubleBinding(source, feedMethod, condition, outputs); + } + } +} diff --git a/src/test/java/arachne/test/io/GettableBooleanTest.java b/src/test/java/arachne/test/io/GettableBooleanTest.java new file mode 100644 index 0000000..0dd013b --- /dev/null +++ b/src/test/java/arachne/test/io/GettableBooleanTest.java @@ -0,0 +1,52 @@ +package arachne.test.io; + +import static arachne.lib.io.GettableBoolean.*; + +import org.junit.jupiter.api.Test; +import static org.junit.jupiter.api.Assertions.*; + +public class GettableBooleanTest +{ + @Test + void constants() { + assertTrue(TRUE); + assertFalse(FALSE); + } + + @Test + void and() { + assertTrue(TRUE.and(TRUE)); + assertFalse(TRUE.and(FALSE)); + assertFalse(FALSE.and(TRUE)); + assertFalse(FALSE.and(FALSE)); + } + + @Test + void or() { + assertTrue(TRUE.or(TRUE)); + assertTrue(TRUE.or(FALSE)); + assertTrue(FALSE.or(TRUE)); + assertFalse(FALSE.or(FALSE)); + } + + @Test + void xor() { + assertFalse(TRUE.xor(TRUE)); + assertTrue(TRUE.xor(FALSE)); + assertTrue(FALSE.xor(TRUE)); + assertFalse(FALSE.xor(FALSE)); + } + + @Test + void is() { + assertTrue(TRUE.is(true)); + assertFalse(TRUE.is(false)); + assertFalse(FALSE.is(true)); + assertTrue(FALSE.is(false)); + + assertTrue(TRUE.is((b) -> b)); + assertFalse(TRUE.is((b) -> !b)); + assertFalse(FALSE.is((b) -> b)); + assertTrue(FALSE.is((b) -> !b)); + } +} diff --git a/src/test/java/arachne/test/logic/LinearMapTest.java b/src/test/java/arachne/test/logic/LinearMapTest.java new file mode 100644 index 0000000..e66bd2e --- /dev/null +++ b/src/test/java/arachne/test/logic/LinearMapTest.java @@ -0,0 +1,104 @@ +package arachne.test.logic; + +import org.junit.jupiter.api.Test; + +import arachne.lib.listeners.DoubleProperty; +import arachne.lib.listeners.SimpleDoubleProperty; +import arachne.lib.logic.LinearMap; +import arachne.lib.pipeline.DoublePipe; +import arachne.lib.pipeline.SimpleDoublePipe; + +import static arachne.lib.logic.DoubleComparison.*; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import static org.junit.jupiter.api.Assertions.*; + +public class LinearMapTest +{ + DoublePipe input; + DoubleProperty output; + + @BeforeEach + void beforeEach() { + input = new SimpleDoublePipe(); + output = new SimpleDoubleProperty(); + } + + @AfterEach + void afterEach() { + input.detachAllOutputs(); + + input = null; + output = null; + } + + @Test + void noBounds() { + input.setModifier(LinearMap.map(-100, 100).to(-5, 3)); + input.attachOutput(output); + + // Regular inputs + input.accept(-100); + assertTrue(output.is(-5)); + + input.accept(0); + assertTrue(output.is(-1)); + + input.accept(100); + assertTrue(output.is(3)); + + // Out of bounds inputs + input.accept(-500); + assertTrue(output.is(-21)); + + input.accept(200); + assertTrue(output.is(7)); + } + + @Test + void noBoundsInverse() { + input.setModifier(LinearMap.map(100, 200).to(10, 5)); + input.attachOutput(output); + + // Regular inputs + input.accept(100); + assertTrue(output.is(10)); + + input.accept(150); + assertTrue(output.is(7.5)); + + input.accept(200); + assertTrue(output.is(5)); + + // Out of bounds inputs + input.accept(-500); + assertTrue(output.is(40)); + + input.accept(300); + assertTrue(output.is(0)); + } + +// @Test +// void stopOnMin() { +// input +// .attachTranslator(LinearMap.map(-1024, 1024).to(-1, 1).until(LinearMap.REACHES_MIN)) +// .attachOutput(new PredicatedDoublePipe()) +// .attachOutput(output); +// +// input.accept(512); +// assertTrue(output.is(0.5)); +// +// input.accept(0); +// assertTrue(output.is(0)); +// +// input.accept(-1023); +// assertTrue(output.is(greaterThan(-1))); +// +// input.accept(-1024); +// assertTrue(output.is(-1)); +// +// input.accept(256); +// assertTrue(output.is(-1)); +// } +}