diff --git a/hal/src/main/java/edu/wpi/first/hal/HALValue.java b/hal/src/main/java/edu/wpi/first/hal/HALValue.java new file mode 100644 index 0000000000..636af8f903 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/HALValue.java @@ -0,0 +1,169 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal; + +@SuppressWarnings("AbbreviationAsWordInName") +public final class HALValue { + public static final int kUnassigned = 0; + public static final int kBoolean = 0x01; + public static final int kDouble = 0x02; + public static final int kEnum = 0x04; + public static final int kInt = 0x08; + public static final int kLong = 0x10; + + private int m_type; + private long m_long; + private double m_double; + + private HALValue(double value, int type) { + m_type = type; + m_double = value; + } + + private HALValue(long value, int type) { + m_type = type; + m_long = value; + } + + private HALValue() { + + } + + /** + * Get the type of the value. + * + * @return Type (e.g. kBoolean). + */ + public int getType() { + return m_type; + } + + /** + * Get the value as a boolean. Does not perform type checking. + * + * @return value contents + */ + public boolean getBoolean() { + return m_long != 0; + } + + /** + * Get the value as a long. Does not perform type checking. + * + * @return value contents + */ + public long getLong() { + return m_long; + } + + /** + * Get the value as a double. Does not perform type checking. + * + * @return value contents + */ + public double getDouble() { + return m_double; + } + + /** + * Get the native long value. Does not perform type checking. + * + * @return value contents + */ + public long getNativeLong() { + return m_long; + } + + /** + * Get the native double value. Does not perform type checking. + * + * @return value contents + */ + public double getNativeDouble() { + return m_double; + } + + /** + * Build a HAL boolean value. + * + * @param value value + * @return HAL value + */ + public static HALValue makeBoolean(boolean value) { + return new HALValue(value ? 1 : 0, kBoolean); + } + + /** + * Build a HAL enum value. + * + * @param value value + * @return HAL value + */ + public static HALValue makeEnum(int value) { + return new HALValue(value, kEnum); + } + + /** + * Build a HAL integer value. + * + * @param value value + * @return HAL value + */ + public static HALValue makeInt(int value) { + return new HALValue(value, kInt); + } + + /** + * Build a HAL long value. + * + * @param value value + * @return HAL value + */ + public static HALValue makeLong(long value) { + return new HALValue(value, kLong); + } + + /** + * Build a HAL double value. + * + * @param value value + * @return HAL value + */ + public static HALValue makeDouble(double value) { + return new HALValue(value, kDouble); + } + + public static HALValue makeUnassigned() { + return new HALValue(); + } + + /** + * Build a HAL value from its native components. + * + * @param type type + * @param value1 long value (all except double) + * @param value2 double value (for double only) + * @return HAL value + */ + public static HALValue fromNative(int type, long value1, double value2) { + switch (type) { + case 0x01: + return makeBoolean(value1 != 0); + case 0x02: + return makeDouble(value2); + case 0x16: + return makeEnum((int) value1); + case 0x32: + return makeInt((int) value1); + case 0x64: + return makeLong(value1); + default: + return makeUnassigned(); + } + } +} diff --git a/hal/src/main/java/edu/wpi/first/hal/SimBoolean.java b/hal/src/main/java/edu/wpi/first/hal/SimBoolean.java new file mode 100644 index 0000000000..00e8552f14 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/SimBoolean.java @@ -0,0 +1,40 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal; + +/** + * A wrapper around a simulator boolean value handle. + */ +public class SimBoolean extends SimValue { + /** + * Wraps a simulated value handle as returned by SimDeviceJNI.createSimValueBoolean(). + * + * @param handle simulated value handle + */ + public SimBoolean(int handle) { + super(handle); + } + + /** + * Gets the simulated value. + * + * @return The current value + */ + public boolean get() { + return SimDeviceJNI.getSimValueBoolean(m_handle); + } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + public void set(boolean value) { + SimDeviceJNI.setSimValueBoolean(m_handle, value); + } +} diff --git a/hal/src/main/java/edu/wpi/first/hal/SimDevice.java b/hal/src/main/java/edu/wpi/first/hal/SimDevice.java new file mode 100644 index 0000000000..ecddc2ef01 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/SimDevice.java @@ -0,0 +1,171 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal; + +/** + * A wrapper around a simulator device handle. + */ +public class SimDevice implements AutoCloseable { + /** + * Creates a simulated device. + * + *

The device name must be unique. Returns null if the device name + * already exists. If multiple instances of the same device are desired, + * recommend appending the instance/unique identifer in brackets to the base + * name, e.g. "device[1]". + * + *

null is returned if not in simulation. + * + * @param name device name + * @return simulated device object + */ + public static SimDevice create(String name) { + int handle = SimDeviceJNI.createSimDevice(name); + if (handle <= 0) { + return null; + } + return new SimDevice(handle); + } + + /** + * Creates a simulated device. + * + *

The device name must be unique. Returns null if the device name + * already exists. This is a convenience method that appends index in + * brackets to the device name, e.g. passing index=1 results in "device[1]" + * for the device name. + * + *

null is returned if not in simulation. + * + * @param name device name + * @param index device index number to append to name + * @return simulated device object + */ + public static SimDevice create(String name, int index) { + return create(name + "[" + index + "]"); + } + + /** + * Creates a simulated device. + * + *

The device name must be unique. Returns null if the device name + * already exists. This is a convenience method that appends index and + * channel in brackets to the device name, e.g. passing index=1 and channel=2 + * results in "device[1,2]" for the device name. + * + *

null is returned if not in simulation. + * + * @param name device name + * @param index device index number to append to name + * @param channel device channel number to append to name + * @return simulated device object + */ + public static SimDevice create(String name, int index, int channel) { + return create(name + "[" + index + "," + channel + "]"); + } + + /** + * Wraps a simulated device handle as returned by SimDeviceJNI.createSimDevice(). + * + * @param handle simulated device handle + */ + public SimDevice(int handle) { + m_handle = handle; + } + + @Override + public void close() { + SimDeviceJNI.freeSimDevice(m_handle); + } + + /** + * Get the internal device handle. + * + * @return internal handle + */ + public int getNativeHandle() { + return m_handle; + } + + /** + * Creates a value on the simulated device. + * + *

Returns null if not in simulation. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value object + */ + public SimValue createValue(String name, boolean readonly, HALValue initialValue) { + int handle = SimDeviceJNI.createSimValue(m_handle, name, readonly, initialValue); + if (handle <= 0) { + return null; + } + return new SimValue(handle); + } + + /** + * Creates a double value on the simulated device. + * + *

Returns null if not in simulation. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated double value object + */ + public SimDouble createDouble(String name, boolean readonly, double initialValue) { + int handle = SimDeviceJNI.createSimValueDouble(m_handle, name, readonly, initialValue); + if (handle <= 0) { + return null; + } + return new SimDouble(handle); + } + + /** + * Creates an enumerated value on the simulated device. + * + *

Enumerated values are always in the range 0 to numOptions-1. + * + *

Returns null if not in simulation. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param options array of option descriptions + * @param initialValue initial value (selection) + * @return simulated enum value object + */ + public SimEnum createEnum(String name, boolean readonly, String[] options, int initialValue) { + int handle = SimDeviceJNI.createSimValueEnum(m_handle, name, readonly, options, initialValue); + if (handle <= 0) { + return null; + } + return new SimEnum(handle); + } + + /** + * Creates a boolean value on the simulated device. + * + *

Returns null if not in simulation. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated boolean value object + */ + public SimBoolean createBoolean(String name, boolean readonly, boolean initialValue) { + int handle = SimDeviceJNI.createSimValueBoolean(m_handle, name, readonly, initialValue); + if (handle <= 0) { + return null; + } + return new SimBoolean(handle); + } + + private final int m_handle; +} diff --git a/hal/src/main/java/edu/wpi/first/hal/SimDeviceJNI.java b/hal/src/main/java/edu/wpi/first/hal/SimDeviceJNI.java new file mode 100644 index 0000000000..d9b8931bc7 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/SimDeviceJNI.java @@ -0,0 +1,183 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal; + +public class SimDeviceJNI extends JNIWrapper { + /** + * Creates a simulated device. + * + *

The device name must be unique. 0 is returned if the device name + * already exists. If multiple instances of the same device are desired, + * recommend appending the instance/unique identifer in brackets to the base + * name, e.g. "device[1]". + * + *

0 is returned if not in simulation. + * + * @param name device name + * @return simulated device handle + */ + public static native int createSimDevice(String name); + + /** + * Frees a simulated device. + * + *

This also allows the same device name to be used again. + * This also frees all the simulated values created on the device. + * + * @param handle simulated device handle + */ + public static native void freeSimDevice(int handle); + + private static native int createSimValueNative(int device, String name, boolean readonly, + int type, long value1, double value2); + + /** + * Creates a value on a simulated device. + * + *

Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value handle + */ + public static int createSimValue(int device, String name, boolean readonly, + HALValue initialValue) { + return createSimValueNative(device, name, readonly, initialValue.getType(), + initialValue.getNativeLong(), initialValue.getNativeDouble()); + } + + /** + * Creates a double value on a simulated device. + * + *

Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value handle + */ + public static int createSimValueDouble(int device, String name, boolean readonly, + double initialValue) { + return createSimValueNative(device, name, readonly, HALValue.kDouble, 0, initialValue); + } + + /** + * Creates an enumerated value on a simulated device. + * + *

Enumerated values are always in the range 0 to numOptions-1. + * + *

Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param options array of option descriptions + * @param initialValue initial value (selection) + * @return simulated value handle + */ + public static native int createSimValueEnum(int device, String name, boolean readonly, + String[] options, int initialValue); + + /** + * Creates a boolean value on a simulated device. + * + *

Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value handle + */ + public static int createSimValueBoolean(int device, String name, boolean readonly, + boolean initialValue) { + return createSimValueNative(device, name, readonly, HALValue.kBoolean, + initialValue ? 1 : 0, 0.0); + } + + /** + * Gets a simulated value. + * + * @param handle simulated value handle + * @return The current value + */ + public static native HALValue getSimValue(int handle); + + /** + * Gets a simulated value (double). + * + * @param handle simulated value handle + * @return The current value + */ + public static native double getSimValueDouble(int handle); + + /** + * Gets a simulated value (enum). + * + * @param handle simulated value handle + * @return The current value + */ + public static native int getSimValueEnum(int handle); + + /** + * Gets a simulated value (boolean). + * + * @param handle simulated value handle + * @return The current value + */ + public static native boolean getSimValueBoolean(int handle); + + private static native void setSimValueNative(int handle, int type, long value1, double value2); + + /** + * Sets a simulated value. + * + * @param handle simulated value handle + * @param value the value to set + */ + public static void setSimValue(int handle, HALValue value) { + setSimValueNative(handle, value.getType(), value.getNativeLong(), value.getNativeDouble()); + } + + /** + * Sets a simulated value (double). + * + * @param handle simulated value handle + * @param value the value to set + */ + public static void setSimValueDouble(int handle, double value) { + setSimValueNative(handle, HALValue.kDouble, 0, value); + } + + /** + * Sets a simulated value (enum). + * + * @param handle simulated value handle + * @param value the value to set + */ + public static void setSimValueEnum(int handle, int value) { + setSimValueNative(handle, HALValue.kEnum, value, 0.0); + } + + /** + * Sets a simulated value (boolean). + * + * @param handle simulated value handle + * @param value the value to set + */ + public static void setSimValueBoolean(int handle, boolean value) { + setSimValueNative(handle, HALValue.kBoolean, value ? 1 : 0, 0.0); + } +} diff --git a/hal/src/main/java/edu/wpi/first/hal/SimDouble.java b/hal/src/main/java/edu/wpi/first/hal/SimDouble.java new file mode 100644 index 0000000000..51c97893ff --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/SimDouble.java @@ -0,0 +1,40 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal; + +/** + * A wrapper around a simulator double value handle. + */ +public class SimDouble extends SimValue { + /** + * Wraps a simulated value handle as returned by SimDeviceJNI.createSimValueDouble(). + * + * @param handle simulated value handle + */ + public SimDouble(int handle) { + super(handle); + } + + /** + * Gets the simulated value. + * + * @return The current value + */ + public double get() { + return SimDeviceJNI.getSimValueDouble(m_handle); + } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + public void set(double value) { + SimDeviceJNI.setSimValueDouble(m_handle, value); + } +} diff --git a/hal/src/main/java/edu/wpi/first/hal/SimEnum.java b/hal/src/main/java/edu/wpi/first/hal/SimEnum.java new file mode 100644 index 0000000000..d951bedd9f --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/SimEnum.java @@ -0,0 +1,40 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal; + +/** + * A wrapper around a simulator enum value handle. + */ +public class SimEnum extends SimValue { + /** + * Wraps a simulated value handle as returned by SimDeviceJNI.createSimValueEnum(). + * + * @param handle simulated value handle + */ + public SimEnum(int handle) { + super(handle); + } + + /** + * Gets the simulated value. + * + * @return The current value + */ + public int get() { + return SimDeviceJNI.getSimValueEnum(m_handle); + } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + public void set(int value) { + SimDeviceJNI.setSimValueEnum(m_handle, value); + } +} diff --git a/hal/src/main/java/edu/wpi/first/hal/SimValue.java b/hal/src/main/java/edu/wpi/first/hal/SimValue.java new file mode 100644 index 0000000000..05d6b0ce3c --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/SimValue.java @@ -0,0 +1,51 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal; + +/** + * A wrapper around a simulator value handle. + */ +public class SimValue { + /** + * Wraps a simulated value handle as returned by SimDeviceJNI.createSimValue(). + * + * @param handle simulated value handle + */ + public SimValue(int handle) { + m_handle = handle; + } + + /** + * Get the internal device handle. + * + * @return internal handle + */ + public int getNativeHandle() { + return m_handle; + } + + /** + * Gets the simulated value. + * + * @return The current value + */ + public HALValue getValue() { + return SimDeviceJNI.getSimValue(m_handle); + } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + public void setValue(HALValue value) { + SimDeviceJNI.setSimValue(m_handle, value); + } + + protected final int m_handle; +} diff --git a/hal/src/main/java/edu/wpi/first/hal/sim/NotifyCallback.java b/hal/src/main/java/edu/wpi/first/hal/sim/NotifyCallback.java index 8781f759bf..9c655e4b48 100644 --- a/hal/src/main/java/edu/wpi/first/hal/sim/NotifyCallback.java +++ b/hal/src/main/java/edu/wpi/first/hal/sim/NotifyCallback.java @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2018-2019 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -7,29 +7,12 @@ package edu.wpi.first.hal.sim; +import edu.wpi.first.hal.HALValue; + public interface NotifyCallback { - void callback(String name, SimValue value); + void callback(String name, HALValue value); default void callbackNative(String name, int type, long value1, double value2) { - switch (type) { - case 0x01: - callback(name, SimValue.makeBoolean(value1 != 0)); - break; - case 0x02: - callback(name, SimValue.makeDouble(value2)); - break; - case 0x16: - callback(name, SimValue.makeEnum((int) value1)); - break; - case 0x32: - callback(name, SimValue.makeInt((int) value1)); - break; - case 0x64: - callback(name, SimValue.makeLong(value1)); - break; - default: - callback(name, SimValue.makeUnassigned()); - break; - } + callback(name, HALValue.fromNative(type, value1, value2)); } } diff --git a/hal/src/main/java/edu/wpi/first/hal/sim/SimDeviceCallback.java b/hal/src/main/java/edu/wpi/first/hal/sim/SimDeviceCallback.java new file mode 100644 index 0000000000..ff9e45b2e3 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/sim/SimDeviceCallback.java @@ -0,0 +1,13 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal.sim; + +@FunctionalInterface +public interface SimDeviceCallback { + void callback(String name, int handle); +} diff --git a/hal/src/main/java/edu/wpi/first/hal/sim/SimValue.java b/hal/src/main/java/edu/wpi/first/hal/sim/SimValue.java deleted file mode 100644 index 3dbb7c9477..0000000000 --- a/hal/src/main/java/edu/wpi/first/hal/sim/SimValue.java +++ /dev/null @@ -1,66 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* Copyright (c) 2018 FIRST. All Rights Reserved. */ -/* Open Source Software - may be modified and shared by FRC teams. The code */ -/* must be accompanied by the FIRST BSD license file in the root directory of */ -/* the project. */ -/*----------------------------------------------------------------------------*/ - -package edu.wpi.first.hal.sim; - -public final class SimValue { - private boolean m_boolean; - private long m_long; - private double m_double; - - private SimValue(boolean b) { - m_boolean = b; - } - - private SimValue(double v) { - m_double = v; - } - - private SimValue(long v) { - m_long = v; - } - - private SimValue() { - - } - - public boolean getBoolean() { - return m_boolean; - } - - public long getLong() { - return m_long; - } - - public double getDouble() { - return m_double; - } - - public static SimValue makeBoolean(boolean value) { - return new SimValue(value); - } - - public static SimValue makeEnum(int value) { - return new SimValue(value); - } - - public static SimValue makeInt(int value) { - return new SimValue(value); - } - - public static SimValue makeLong(long value) { - return new SimValue(value); - } - - public static SimValue makeDouble(double value) { - return new SimValue(value); - } - - public static SimValue makeUnassigned() { - return new SimValue(); - } -} diff --git a/hal/src/main/java/edu/wpi/first/hal/sim/SimValueCallback.java b/hal/src/main/java/edu/wpi/first/hal/sim/SimValueCallback.java new file mode 100644 index 0000000000..786158acf4 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/sim/SimValueCallback.java @@ -0,0 +1,19 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal.sim; + +import edu.wpi.first.hal.HALValue; + +@FunctionalInterface +public interface SimValueCallback { + void callback(String name, int handle, boolean readonly, HALValue value); + + default void callbackNative(String name, int handle, boolean readonly, int type, long value1, double value2) { + callback(name, handle, readonly, HALValue.fromNative(type, value1, value2)); + } +} diff --git a/hal/src/main/java/edu/wpi/first/hal/sim/mockdata/SimDeviceDataJNI.java b/hal/src/main/java/edu/wpi/first/hal/sim/mockdata/SimDeviceDataJNI.java new file mode 100644 index 0000000000..2b8a38ab93 --- /dev/null +++ b/hal/src/main/java/edu/wpi/first/hal/sim/mockdata/SimDeviceDataJNI.java @@ -0,0 +1,73 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.hal.sim.mockdata; + +import edu.wpi.first.hal.sim.SimDeviceCallback; +import edu.wpi.first.hal.sim.SimValueCallback; +import edu.wpi.first.hal.HALValue; +import edu.wpi.first.hal.JNIWrapper; + +public class SimDeviceDataJNI extends JNIWrapper { + public static native int registerSimDeviceCreatedCallback(String prefix, SimDeviceCallback callback, boolean initialNotify); + public static native void cancelSimDeviceCreatedCallback(int uid); + + public static native int registerSimDeviceFreedCallback(String prefix, SimDeviceCallback callback); + public static native void cancelSimDeviceFreedCallback(int uid); + + public static native int getSimDeviceHandle(String name); + + public static native int getSimValueDeviceHandle(int handle); + + public static class SimDeviceInfo { + public SimDeviceInfo(String name, int handle) { + this.name = name; + this.handle = handle; + } + + @SuppressWarnings("MemberName") + String name; + + @SuppressWarnings("MemberName") + int handle; + } + public static native SimDeviceInfo[] enumerateSimDevices(String prefix); + + public static native int registerSimValueCreatedCallback(int device, SimValueCallback callback, boolean initialNotify); + public static native void cancelSimValueCreatedCallback(int uid); + + public static native int registerSimValueChangedCallback(int handle, SimValueCallback callback, boolean initialNotify); + public static native void cancelSimValueChangedCallback(int uid); + + public static native int getSimValueHandle(int device, String name); + + public static class SimValueInfo { + public SimValueInfo(String name, int handle, boolean readonly, int type, long value1, double value2) { + this.name = name; + this.handle = handle; + this.readonly = readonly; + this.value = HALValue.fromNative(type, value1, value2); + } + + @SuppressWarnings("MemberName") + String name; + + @SuppressWarnings("MemberName") + int handle; + + @SuppressWarnings("MemberName") + boolean readonly; + + @SuppressWarnings("MemberName") + HALValue value; + } + public static native SimValueInfo[] enumerateSimValues(int device); + + public static native String[] getSimValueEnumOptions(int handle); + + public static native void resetSimDeviceData(); +} diff --git a/hal/src/main/native/athena/SimDevice.cpp b/hal/src/main/native/athena/SimDevice.cpp new file mode 100644 index 0000000000..94a65d49fe --- /dev/null +++ b/hal/src/main/native/athena/SimDevice.cpp @@ -0,0 +1,41 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "hal/SimDevice.h" + +extern "C" { + +HAL_SimDeviceHandle HAL_CreateSimDevice(const char* name) { return 0; } + +void HAL_FreeSimDevice(HAL_SimDeviceHandle handle) {} + +HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device, + const char* name, HAL_Bool readonly, + const struct HAL_Value* initialValue) { + return 0; +} + +HAL_SimValueHandle HAL_CreateSimValueEnum(HAL_SimDeviceHandle device, + const char* name, HAL_Bool readonly, + int32_t numOptions, + const char** options, + int32_t initialValue) { + return 0; +} + +void HAL_GetSimValue(HAL_SimValueHandle handle, struct HAL_Value* value) { + value->type = HAL_UNASSIGNED; +} + +void HAL_SetSimValue(HAL_SimValueHandle handle, const struct HAL_Value* value) { +} + +hal::SimDevice::SimDevice(const char* name, int index) {} + +hal::SimDevice::SimDevice(const char* name, int index, int channel) {} + +} // extern "C" diff --git a/hal/src/main/native/cpp/jni/HALUtil.cpp b/hal/src/main/native/cpp/jni/HALUtil.cpp index 46c068b544..26b791914a 100644 --- a/hal/src/main/native/cpp/jni/HALUtil.cpp +++ b/hal/src/main/native/cpp/jni/HALUtil.cpp @@ -49,13 +49,15 @@ static JClass canStatusCls; static JClass matchInfoDataCls; static JClass accumulatorResultCls; static JClass canDataCls; +static JClass halValueCls; static const JClassInit classes[] = { {"edu/wpi/first/hal/PWMConfigDataResult", &pwmConfigDataResultCls}, {"edu/wpi/first/hal/can/CANStatus", &canStatusCls}, {"edu/wpi/first/hal/MatchInfoData", &matchInfoDataCls}, {"edu/wpi/first/hal/AccumulatorResult", &accumulatorResultCls}, - {"edu/wpi/first/hal/CANData", &canDataCls}}; + {"edu/wpi/first/hal/CANData", &canDataCls}, + {"edu/wpi/first/hal/HALValue", &halValueCls}}; static const JExceptionInit exceptions[] = { {"java/lang/IllegalArgumentException", &illegalArgExCls}, @@ -224,8 +226,9 @@ jobject CreatePWMConfigDataResult(JNIEnv* env, int32_t maxPwm, int32_t deadbandMinPwm, int32_t minPwm) { static jmethodID constructor = env->GetMethodID(pwmConfigDataResultCls, "", "(IIIII)V"); - return env->NewObject(pwmConfigDataResultCls, constructor, maxPwm, - deadbandMaxPwm, centerPwm, deadbandMinPwm, minPwm); + return env->NewObject(pwmConfigDataResultCls, constructor, (jint)maxPwm, + (jint)deadbandMaxPwm, (jint)centerPwm, + (jint)deadbandMinPwm, (jint)minPwm); } void SetCanStatusObject(JNIEnv* env, jobject canStatus, @@ -271,6 +274,34 @@ jbyteArray SetCANDataObject(JNIEnv* env, jobject canData, int32_t length, return retVal; } +jobject CreateHALValue(JNIEnv* env, const HAL_Value& value) { + static jmethodID fromNative = env->GetStaticMethodID( + halValueCls, "fromNative", "(IJD)Ledu/wpi/first/hal/HALValue;"); + jlong value1 = 0; + jdouble value2 = 0.0; + switch (value.type) { + case HAL_BOOLEAN: + value1 = value.data.v_boolean; + break; + case HAL_DOUBLE: + value2 = value.data.v_double; + break; + case HAL_ENUM: + value1 = value.data.v_enum; + break; + case HAL_INT: + value1 = value.data.v_int; + break; + case HAL_LONG: + value1 = value.data.v_long; + break; + default: + break; + } + return env->CallStaticObjectMethod(halValueCls, fromNative, (jint)value.type, + value1, value2); +} + JavaVM* GetJVM() { return jvm; } } // namespace frc diff --git a/hal/src/main/native/cpp/jni/HALUtil.h b/hal/src/main/native/cpp/jni/HALUtil.h index 8197e1a766..c035f75ab1 100644 --- a/hal/src/main/native/cpp/jni/HALUtil.h +++ b/hal/src/main/native/cpp/jni/HALUtil.h @@ -1,5 +1,5 @@ /*----------------------------------------------------------------------------*/ -/* Copyright (c) 2016-2018 FIRST. All Rights Reserved. */ +/* Copyright (c) 2016-2019 FIRST. All Rights Reserved. */ /* Open Source Software - may be modified and shared by FRC teams. The code */ /* must be accompanied by the FIRST BSD license file in the root directory of */ /* the project. */ @@ -14,6 +14,7 @@ #include struct HAL_MatchInfo; +struct HAL_Value; namespace frc { @@ -67,6 +68,8 @@ void SetAccumulatorResultObject(JNIEnv* env, jobject accumulatorResult, jbyteArray SetCANDataObject(JNIEnv* env, jobject canData, int32_t length, uint64_t timestamp); +jobject CreateHALValue(JNIEnv* env, const HAL_Value& value); + JavaVM* GetJVM(); } // namespace frc diff --git a/hal/src/main/native/cpp/jni/SimDeviceJNI.cpp b/hal/src/main/native/cpp/jni/SimDeviceJNI.cpp new file mode 100644 index 0000000000..d9652ccf2b --- /dev/null +++ b/hal/src/main/native/cpp/jni/SimDeviceJNI.cpp @@ -0,0 +1,168 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include + +#include + +#include "HALUtil.h" +#include "edu_wpi_first_hal_SimDeviceJNI.h" +#include "hal/SimDevice.h" + +using namespace wpi::java; + +static HAL_Value ValueFromJava(jint type, jlong value1, jdouble value2) { + HAL_Value value; + value.type = static_cast(type); + switch (value.type) { + case HAL_BOOLEAN: + value.data.v_boolean = value1; + break; + case HAL_DOUBLE: + value.data.v_double = value2; + break; + case HAL_ENUM: + value.data.v_enum = value1; + break; + case HAL_INT: + value.data.v_int = value1; + break; + case HAL_LONG: + value.data.v_long = value1; + break; + default: + break; + } + return value; +} + +extern "C" { + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: createSimDevice + * Signature: (Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_createSimDevice + (JNIEnv* env, jclass, jstring name) +{ + return HAL_CreateSimDevice(JStringRef{env, name}.c_str()); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: freeSimDevice + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_freeSimDevice + (JNIEnv*, jclass, jint handle) +{ + HAL_FreeSimDevice(handle); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: createSimValueNative + * Signature: (ILjava/lang/String;ZIJD)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_createSimValueNative + (JNIEnv* env, jclass, jint device, jstring name, jboolean readonly, jint type, + jlong value1, jdouble value2) +{ + return HAL_CreateSimValue(device, JStringRef{env, name}.c_str(), readonly, + ValueFromJava(type, value1, value2)); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: createSimValueEnum + * Signature: (ILjava/lang/String;Z[Ljava/lang/Object;I)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_createSimValueEnum + (JNIEnv* env, jclass, jint device, jstring name, jboolean readonly, + jobjectArray options, jint initialValue) +{ + size_t len = env->GetArrayLength(options); + std::vector arr; + arr.reserve(len); + for (size_t i = 0; i < len; ++i) { + JLocal elem{ + env, static_cast(env->GetObjectArrayElement(options, i))}; + if (!elem) return 0; + arr.push_back(JStringRef{env, elem}.str()); + } + wpi::SmallVector carr; + for (auto&& val : arr) carr.push_back(val.c_str()); + return HAL_CreateSimValueEnum(device, JStringRef{env, name}.c_str(), readonly, + len, carr.data(), initialValue); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: getSimValue + * Signature: (I)Ljava/lang/Object; + */ +JNIEXPORT jobject JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_getSimValue + (JNIEnv* env, jclass, jint handle) +{ + return frc::CreateHALValue(env, HAL_GetSimValue(handle)); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: getSimValueDouble + * Signature: (I)D + */ +JNIEXPORT jdouble JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_getSimValueDouble + (JNIEnv*, jclass, jint handle) +{ + return HAL_GetSimValueDouble(handle); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: getSimValueEnum + * Signature: (I)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_getSimValueEnum + (JNIEnv*, jclass, jint handle) +{ + return HAL_GetSimValueEnum(handle); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: getSimValueBoolean + * Signature: (I)Z + */ +JNIEXPORT jboolean JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_getSimValueBoolean + (JNIEnv*, jclass, jint handle) +{ + return HAL_GetSimValueBoolean(handle); +} + +/* + * Class: edu_wpi_first_hal_SimDeviceJNI + * Method: setSimValueNative + * Signature: (IIJD)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_SimDeviceJNI_setSimValueNative + (JNIEnv*, jclass, jint handle, jint type, jlong value1, jdouble value2) +{ + HAL_SetSimValue(handle, ValueFromJava(type, value1, value2)); +} + +} // extern "C" diff --git a/hal/src/main/native/include/hal/HAL.h b/hal/src/main/native/include/hal/HAL.h index 974be0a54a..2f71d796eb 100644 --- a/hal/src/main/native/include/hal/HAL.h +++ b/hal/src/main/native/include/hal/HAL.h @@ -36,6 +36,7 @@ #include "hal/Relay.h" #include "hal/SPI.h" #include "hal/SerialPort.h" +#include "hal/SimDevice.h" #include "hal/Solenoid.h" #include "hal/Threads.h" #include "hal/Types.h" diff --git a/hal/src/main/native/include/hal/SimDevice.h b/hal/src/main/native/include/hal/SimDevice.h new file mode 100644 index 0000000000..b05021e5ee --- /dev/null +++ b/hal/src/main/native/include/hal/SimDevice.h @@ -0,0 +1,610 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +#ifdef __cplusplus +#include + +#include +#endif + +#include "hal/Types.h" +#include "hal/Value.h" + +/** + * @defgroup hal_simdevice Simulator Device Framework + * @ingroup hal_capi + * HAL Simulator Device Framework. This enables creating simulation-only + * variables for higher level device access. For example, a device such as + * a SPI gyro can expose angle and rate variables to enable direct access + * from simulation extensions or user test code instead of requiring that + * the SPI bit-level protocol be implemented in simulation code. + * + * @{ + */ + +#ifdef __cplusplus +extern "C" { +#endif + +/** + * Creates a simulated device. + * + * The device name must be unique. 0 is returned if the device name already + * exists. If multiple instances of the same device are desired, recommend + * appending the instance/unique identifer in brackets to the base name, + * e.g. "device[1]". + * + * 0 is returned if not in simulation. + * + * @param name device name + * @return simulated device handle + */ +HAL_SimDeviceHandle HAL_CreateSimDevice(const char* name); + +/** + * Frees a simulated device. + * + * This also allows the same device name to be used again. + * This also frees all the simulated values created on the device. + * + * @param handle simulated device handle + */ +void HAL_FreeSimDevice(HAL_SimDeviceHandle handle); + +/** + * Creates a value on a simulated device. + * + * Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value handle + */ +HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device, + const char* name, HAL_Bool readonly, + const struct HAL_Value* initialValue); + +#ifdef __cplusplus +extern "C++" { +inline HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device, + const char* name, + HAL_Bool readonly, + const HAL_Value& initialValue) { + return HAL_CreateSimValue(device, name, readonly, &initialValue); +} +} // extern "C++" +#endif + +/** + * Creates a double value on a simulated device. + * + * Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value handle + */ +inline HAL_SimValueHandle HAL_CreateSimValueDouble(HAL_SimDeviceHandle device, + const char* name, + HAL_Bool readonly, + double initialValue) { + struct HAL_Value v = HAL_MakeDouble(initialValue); + return HAL_CreateSimValue(device, name, readonly, &v); +} + +/** + * Creates an enumerated value on a simulated device. + * + * Enumerated values are always in the range 0 to numOptions-1. + * + * Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param numOptions number of enumerated value options (length of options) + * @param options array of option descriptions + * @param initialValue initial value (selection) + * @return simulated value handle + */ +HAL_SimValueHandle HAL_CreateSimValueEnum(HAL_SimDeviceHandle device, + const char* name, HAL_Bool readonly, + int32_t numOptions, + const char** options, + int32_t initialValue); + +/** + * Creates a boolean value on a simulated device. + * + * Returns 0 if not in simulation; this can be used to avoid calls + * to Set/Get functions. + * + * @param device simulated device handle + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value handle + */ +inline HAL_SimValueHandle HAL_CreateSimValueBoolean(HAL_SimDeviceHandle device, + const char* name, + HAL_Bool readonly, + HAL_Bool initialValue) { + struct HAL_Value v = HAL_MakeBoolean(initialValue); + return HAL_CreateSimValue(device, name, readonly, &v); +} + +/** + * Gets a simulated value. + * + * @param handle simulated value handle + * @param value value (output parameter) + */ +void HAL_GetSimValue(HAL_SimValueHandle handle, struct HAL_Value* value); + +#ifdef __cplusplus +extern "C++" { +inline HAL_Value HAL_GetSimValue(HAL_SimValueHandle handle) { + HAL_Value v; + HAL_GetSimValue(handle, &v); + return v; +} +} // extern "C++" +#endif + +/** + * Gets a simulated value (double). + * + * @param handle simulated value handle + * @return The current value + */ +inline double HAL_GetSimValueDouble(HAL_SimValueHandle handle) { + struct HAL_Value v; + HAL_GetSimValue(handle, &v); + return v.type == HAL_DOUBLE ? v.data.v_double : 0.0; +} + +/** + * Gets a simulated value (enum). + * + * @param handle simulated value handle + * @return The current value + */ +inline int32_t HAL_GetSimValueEnum(HAL_SimValueHandle handle) { + struct HAL_Value v; + HAL_GetSimValue(handle, &v); + return v.type == HAL_ENUM ? v.data.v_enum : 0; +} + +/** + * Gets a simulated value (boolean). + * + * @param handle simulated value handle + * @return The current value + */ +inline HAL_Bool HAL_GetSimValueBoolean(HAL_SimValueHandle handle) { + struct HAL_Value v; + HAL_GetSimValue(handle, &v); + return v.type == HAL_BOOLEAN ? v.data.v_boolean : 0; +} + +/** + * Sets a simulated value. + * + * @param handle simulated value handle + * @param value the value to set + */ +void HAL_SetSimValue(HAL_SimValueHandle handle, const struct HAL_Value* value); + +#ifdef __cplusplus +extern "C++" { +inline void HAL_SetSimValue(HAL_SimValueHandle handle, const HAL_Value& value) { + HAL_SetSimValue(handle, &value); +} +} // extern "C++" +#endif + +/** + * Sets a simulated value (double). + * + * @param handle simulated value handle + * @param value the value to set + */ +inline void HAL_SetSimValueDouble(HAL_SimValueHandle handle, double value) { + struct HAL_Value v = HAL_MakeDouble(value); + HAL_SetSimValue(handle, &v); +} + +/** + * Sets a simulated value (enum). + * + * @param handle simulated value handle + * @param value the value to set + */ +inline void HAL_SetSimValueEnum(HAL_SimValueHandle handle, int32_t value) { + struct HAL_Value v = HAL_MakeEnum(value); + HAL_SetSimValue(handle, &v); +} + +/** + * Sets a simulated value (boolean). + * + * @param handle simulated value handle + * @param value the value to set + */ +inline void HAL_SetSimValueBoolean(HAL_SimValueHandle handle, HAL_Bool value) { + struct HAL_Value v = HAL_MakeBoolean(value); + HAL_SetSimValue(handle, &v); +} + +/** @} */ + +#ifdef __cplusplus +} // extern "C" +#endif + +#ifdef __cplusplus +namespace hal { + +/** + * C++ wrapper around a HAL simulator value handle. + */ +class SimValue { + public: + /** + * Default constructor that results in an "empty" object that is false in + * a boolean context. + */ + SimValue() = default; + + /** + * Wraps a simulated value handle as returned by HAL_CreateSimValue(). + * + * @param handle simulated value handle + */ + /*implicit*/ SimValue(HAL_SimValueHandle val) // NOLINT(runtime/explicit) + : m_handle(val) {} + + /** + * Determine if handle is empty. Should be used to optimize out code paths + * that are taken/not taken in simulation. + * + * @return False if handle is empty, true if handle is valid. + */ + explicit operator bool() const { return m_handle != HAL_kInvalidHandle; } + + /** + * Get the internal device handle. + * + * @return internal handle + */ + operator HAL_SimValueHandle() const { return m_handle; } + + /** + * Gets the simulated value. + * + * @return The current value + */ + HAL_Value GetValue() const { return HAL_GetSimValue(m_handle); } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + void SetValue(const HAL_Value& value) { HAL_SetSimValue(m_handle, value); } + + protected: + HAL_SimValueHandle m_handle = HAL_kInvalidHandle; +}; + +/** + * C++ wrapper around a HAL simulator double value handle. + */ +class SimDouble : public SimValue { + public: + /** + * Default constructor that results in an "empty" object that is false in + * a boolean context. + */ + SimDouble() = default; + + /** + * Wraps a simulated value handle as returned by HAL_CreateSimValueDouble(). + * + * @param handle simulated value handle + */ + /*implicit*/ SimDouble(HAL_SimValueHandle val) // NOLINT(runtime/explicit) + : SimValue(val) {} + + /** + * Gets the simulated value. + * + * @return The current value + */ + double Get() const { return HAL_GetSimValueDouble(m_handle); } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + void Set(double value) { HAL_SetSimValueDouble(m_handle, value); } +}; + +/** + * C++ wrapper around a HAL simulator enum value handle. + */ +class SimEnum : public SimValue { + public: + /** + * Default constructor that results in an "empty" object that is false in + * a boolean context. + */ + SimEnum() = default; + + /** + * Wraps a simulated value handle as returned by HAL_CreateSimValueEnum(). + * + * @param handle simulated value handle + */ + /*implicit*/ SimEnum(HAL_SimValueHandle val) // NOLINT(runtime/explicit) + : SimValue(val) {} + + /** + * Gets the simulated value. + * + * @return The current value + */ + int32_t Get() const { return HAL_GetSimValueEnum(m_handle); } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + void Set(int32_t value) { HAL_SetSimValueEnum(m_handle, value); } +}; + +/** + * C++ wrapper around a HAL simulator boolean value handle. + */ +class SimBoolean : public SimValue { + public: + /** + * Default constructor that results in an "empty" object that is false in + * a boolean context. + */ + SimBoolean() = default; + + /** + * Wraps a simulated value handle as returned by HAL_CreateSimValueBoolean(). + * + * @param handle simulated value handle + */ + /*implicit*/ SimBoolean(HAL_SimValueHandle val) // NOLINT(runtime/explicit) + : SimValue(val) {} + + /** + * Gets the simulated value. + * + * @return The current value + */ + bool Get() const { return HAL_GetSimValueBoolean(m_handle); } + + /** + * Sets the simulated value. + * + * @param value the value to set + */ + void Set(bool value) { HAL_SetSimValueBoolean(m_handle, value); } +}; + +/** + * A move-only C++ wrapper around a HAL simulator device handle. + */ +class SimDevice { + public: + /** + * Default constructor that results in an "empty" object that is false in + * a boolean context. + */ + SimDevice() = default; + + /** + * Creates a simulated device. + * + * The device name must be unique. Returns null if the device name + * already exists. If multiple instances of the same device are desired, + * recommend appending the instance/unique identifer in brackets to the base + * name, e.g. "device[1]". + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name device name + */ + explicit SimDevice(const char* name) : m_handle(HAL_CreateSimDevice(name)) {} + + /** + * Creates a simulated device. + * + * The device name must be unique. Returns null if the device name + * already exists. This is a convenience method that appends index in + * brackets to the device name, e.g. passing index=1 results in "device[1]" + * for the device name. + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name device name + * @param index device index number to append to name + */ + SimDevice(const char* name, int index); + + /** + * Creates a simulated device. + * + * The device name must be unique. Returns null if the device name + * already exists. This is a convenience method that appends index and + * channel in brackets to the device name, e.g. passing index=1 and channel=2 + * results in "device[1,2]" for the device name. + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name device name + * @param index device index number to append to name + * @param channel device channel number to append to name + */ + SimDevice(const char* name, int index, int channel); + + /** + * Wraps a simulated device handle as returned by HAL_CreateSimDevice(). + * + * @param handle simulated device handle + */ + /*implicit*/ SimDevice(HAL_SimDeviceHandle val) // NOLINT(runtime/explicit) + : m_handle(val) {} + + ~SimDevice() { + if (m_handle != HAL_kInvalidHandle) HAL_FreeSimDevice(m_handle); + } + + SimDevice(const SimDevice&) = delete; + SimDevice& operator=(const SimDevice&) = delete; + + SimDevice(SimDevice&& rhs) : m_handle(rhs.m_handle) { + rhs.m_handle = HAL_kInvalidHandle; + } + + SimDevice& operator=(SimDevice&& rhs) { + m_handle = rhs.m_handle; + rhs.m_handle = HAL_kInvalidHandle; + return *this; + } + + /** + * Determine if handle is empty. Should be used to optimize out code paths + * that are taken/not taken in simulation. + * + * @return False if handle is empty, true if handle is valid. + */ + explicit operator bool() const { return m_handle != HAL_kInvalidHandle; } + + /** + * Get the internal device handle. + * + * @return internal handle + */ + operator HAL_SimDeviceHandle() const { return m_handle; } + + /** + * Creates a value on the simulated device. + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated value object + */ + SimValue CreateValue(const char* name, bool readonly, + const HAL_Value& initialValue) { + return HAL_CreateSimValue(m_handle, name, readonly, &initialValue); + } + + /** + * Creates a double value on the simulated device. + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated double value object + */ + SimDouble CreateDouble(const char* name, bool readonly, double initialValue) { + return HAL_CreateSimValueDouble(m_handle, name, readonly, initialValue); + } + + /** + * Creates an enumerated value on the simulated device. + * + * Enumerated values are always in the range 0 to numOptions-1. + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param options array of option descriptions + * @param initialValue initial value (selection) + * @return simulated enum value object + */ + SimEnum CreateEnum(const char* name, bool readonly, + std::initializer_list options, + int32_t initialValue) { + return HAL_CreateSimValueEnum(m_handle, name, readonly, options.size(), + const_cast(options.begin()), + initialValue); + } + + /** + * Creates an enumerated value on the simulated device. + * + * Enumerated values are always in the range 0 to numOptions-1. + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param options array of option descriptions + * @param initialValue initial value (selection) + * @return simulated enum value object + */ + SimEnum CreateEnum(const char* name, bool readonly, + wpi::ArrayRef options, int32_t initialValue) { + return HAL_CreateSimValueEnum(m_handle, name, readonly, options.size(), + const_cast(options.data()), + initialValue); + } + + /** + * Creates a boolean value on the simulated device. + * + * If not in simulation, results in an "empty" object that evaluates to false + * in a boolean context. + * + * @param name value name + * @param readonly if the value should not be written from simulation side + * @param initialValue initial value + * @return simulated boolean value object + */ + SimBoolean CreateBoolean(const char* name, bool readonly, bool initialValue) { + return HAL_CreateSimValueBoolean(m_handle, name, readonly, initialValue); + } + + protected: + HAL_SimDeviceHandle m_handle = HAL_kInvalidHandle; +}; + +} // namespace hal +#endif // __cplusplus diff --git a/hal/src/main/native/include/hal/Value.h b/hal/src/main/native/include/hal/Value.h index 70e465ce02..578d989046 100644 --- a/hal/src/main/native/include/hal/Value.h +++ b/hal/src/main/native/include/hal/Value.h @@ -14,9 +14,9 @@ enum HAL_Type { HAL_UNASSIGNED = 0, HAL_BOOLEAN = 0x01, HAL_DOUBLE = 0x02, - HAL_ENUM = 0x16, - HAL_INT = 0x32, - HAL_LONG = 0x64, + HAL_ENUM = 0x04, + HAL_INT = 0x08, + HAL_LONG = 0x10, }; /** HAL Entry Value. Note this is a typed union. */ diff --git a/hal/src/main/native/include/mockdata/SimDeviceData.h b/hal/src/main/native/include/mockdata/SimDeviceData.h new file mode 100644 index 0000000000..44398d78e8 --- /dev/null +++ b/hal/src/main/native/include/mockdata/SimDeviceData.h @@ -0,0 +1,73 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include "NotifyListener.h" +#include "hal/Types.h" +#include "hal/Value.h" + +typedef void (*HALSIM_SimDeviceCallback)(const char* name, void* param, + HAL_SimDeviceHandle handle); + +typedef void (*HALSIM_SimValueCallback)(const char* name, void* param, + HAL_SimValueHandle handle, + HAL_Bool readonly, + const struct HAL_Value* value); + +#ifdef __cplusplus +extern "C" { +#endif + +int32_t HALSIM_RegisterSimDeviceCreatedCallback( + const char* prefix, void* param, HALSIM_SimDeviceCallback callback, + HAL_Bool initialNotify); + +void HALSIM_CancelSimDeviceCreatedCallback(int32_t uid); + +int32_t HALSIM_RegisterSimDeviceFreedCallback( + const char* prefix, void* param, HALSIM_SimDeviceCallback callback); + +void HALSIM_CancelSimDeviceFreedCallback(int32_t uid); + +HAL_SimDeviceHandle HALSIM_GetSimDeviceHandle(const char* name); + +const char* HALSIM_GetSimDeviceName(HAL_SimDeviceHandle handle); + +HAL_SimDeviceHandle HALSIM_GetSimValueDeviceHandle(HAL_SimValueHandle handle); + +void HALSIM_EnumerateSimDevices(const char* prefix, void* param, + HALSIM_SimDeviceCallback callback); + +int32_t HALSIM_RegisterSimValueCreatedCallback(HAL_SimDeviceHandle device, + void* param, + HALSIM_SimValueCallback callback, + HAL_Bool initialNotify); + +void HALSIM_CancelSimValueCreatedCallback(int32_t uid); + +int32_t HALSIM_RegisterSimValueChangedCallback(HAL_SimValueHandle handle, + void* param, + HALSIM_SimValueCallback callback, + HAL_Bool initialNotify); + +void HALSIM_CancelSimValueChangedCallback(int32_t uid); + +HAL_SimValueHandle HALSIM_GetSimValueHandle(HAL_SimDeviceHandle device, + const char* name); + +void HALSIM_EnumerateSimValues(HAL_SimDeviceHandle device, void* param, + HALSIM_SimValueCallback callback); + +const char** HALSIM_GetSimValueEnumOptions(HAL_SimValueHandle handle, + int32_t* numOptions); + +void HALSIM_ResetSimDeviceData(void); + +#ifdef __cplusplus +} // extern "C" +#endif diff --git a/hal/src/main/native/sim/HAL.cpp b/hal/src/main/native/sim/HAL.cpp index 191cfdcbb0..cf019cfcc4 100644 --- a/hal/src/main/native/sim/HAL.cpp +++ b/hal/src/main/native/sim/HAL.cpp @@ -41,6 +41,7 @@ void InitializeHAL() { InitializePWMData(); InitializeRelayData(); InitializeRoboRioData(); + InitializeSimDeviceData(); InitializeSPIAccelerometerData(); InitializeSPIData(); InitializeAccelerometer(); @@ -69,6 +70,7 @@ void InitializeHAL() { InitializePWM(); InitializeRelay(); InitializeSerialPort(); + InitializeSimDevice(); InitializeSolenoid(); InitializeSPI(); InitializeThreads(); diff --git a/hal/src/main/native/sim/HALInitializer.h b/hal/src/main/native/sim/HALInitializer.h index c765042f3f..4c91b404fe 100644 --- a/hal/src/main/native/sim/HALInitializer.h +++ b/hal/src/main/native/sim/HALInitializer.h @@ -35,6 +35,7 @@ extern void InitializePDPData(); extern void InitializePWMData(); extern void InitializeRelayData(); extern void InitializeRoboRioData(); +extern void InitializeSimDeviceData(); extern void InitializeSPIAccelerometerData(); extern void InitializeSPIData(); extern void InitializeAccelerometer(); @@ -64,6 +65,7 @@ extern void InitializePower(); extern void InitializePWM(); extern void InitializeRelay(); extern void InitializeSerialPort(); +extern void InitializeSimDevice(); extern void InitializeSolenoid(); extern void InitializeSPI(); extern void InitializeThreads(); diff --git a/hal/src/main/native/sim/SimDevice.cpp b/hal/src/main/native/sim/SimDevice.cpp new file mode 100644 index 0000000000..a8f8f8048e --- /dev/null +++ b/hal/src/main/native/sim/SimDevice.cpp @@ -0,0 +1,75 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "hal/SimDevice.h" + +#include +#include + +#include "HALInitializer.h" +#include "mockdata/SimDeviceDataInternal.h" + +using namespace hal; + +namespace hal { +namespace init { +void InitializeSimDevice() {} +} // namespace init +} // namespace hal + +extern "C" { + +HAL_SimDeviceHandle HAL_CreateSimDevice(const char* name) { + hal::init::CheckInit(); + return SimSimDeviceData->CreateDevice(name); +} + +void HAL_FreeSimDevice(HAL_SimDeviceHandle handle) { + SimSimDeviceData->FreeDevice(handle); +} + +HAL_SimValueHandle HAL_CreateSimValue(HAL_SimDeviceHandle device, + const char* name, HAL_Bool readonly, + const struct HAL_Value* initialValue) { + return SimSimDeviceData->CreateValue(device, name, readonly, 0, nullptr, + *initialValue); +} + +HAL_SimValueHandle HAL_CreateSimValueEnum(HAL_SimDeviceHandle device, + const char* name, HAL_Bool readonly, + int32_t numOptions, + const char** options, + int32_t initialValue) { + return SimSimDeviceData->CreateValue(device, name, readonly, numOptions, + options, HAL_MakeEnum(initialValue)); +} + +void HAL_GetSimValue(HAL_SimValueHandle handle, struct HAL_Value* value) { + *value = SimSimDeviceData->GetValue(handle); +} + +void HAL_SetSimValue(HAL_SimValueHandle handle, const struct HAL_Value* value) { + SimSimDeviceData->SetValue(handle, *value); +} + +hal::SimDevice::SimDevice(const char* name, int index) { + wpi::SmallString<128> fullname; + wpi::raw_svector_ostream os(fullname); + os << name << '[' << index << ']'; + + m_handle = HAL_CreateSimDevice(fullname.c_str()); +} + +hal::SimDevice::SimDevice(const char* name, int index, int channel) { + wpi::SmallString<128> fullname; + wpi::raw_svector_ostream os(fullname); + os << name << '[' << index << ',' << channel << ']'; + + m_handle = HAL_CreateSimDevice(fullname.c_str()); +} + +} // extern "C" diff --git a/hal/src/main/native/sim/jni/SimDeviceDataJNI.cpp b/hal/src/main/native/sim/jni/SimDeviceDataJNI.cpp new file mode 100644 index 0000000000..f6cd05e7e9 --- /dev/null +++ b/hal/src/main/native/sim/jni/SimDeviceDataJNI.cpp @@ -0,0 +1,573 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "SimDeviceDataJNI.h" + +#include + +#include +#include +#include + +#include +#include + +#include "SimulatorJNI.h" +#include "edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI.h" +#include "mockdata/SimDeviceData.h" + +using namespace wpi::java; + +static JClass simDeviceInfoCls; +static JClass simValueInfoCls; +static JClass simDeviceCallbackCls; +static JClass simValueCallbackCls; +static jmethodID simDeviceCallbackCallback; +static jmethodID simValueCallbackCallback; + +namespace { + +struct DeviceInfo { + DeviceInfo(const char* name_, HAL_SimDeviceHandle handle_) + : name{name_}, handle{handle_} {} + std::string name; + HAL_SimValueHandle handle; + + jobject MakeJava(JNIEnv* env) const; + void CallJava(JNIEnv* env, jobject callobj) const; +}; + +struct ValueInfo { + ValueInfo(const char* name_, HAL_SimValueHandle handle_, bool readonly_, + const HAL_Value& value_) + : name{name_}, handle{handle_}, readonly{readonly_}, value{value_} {} + std::string name; + HAL_SimValueHandle handle; + bool readonly; + HAL_Value value; + + jobject MakeJava(JNIEnv* env) const; + void CallJava(JNIEnv* env, jobject callobj) const; + + private: + std::pair ToValue12() const; +}; + +} // namespace + +jobject DeviceInfo::MakeJava(JNIEnv* env) const { + static jmethodID func = + env->GetMethodID(simDeviceInfoCls, "", "(Ljava/lang/String;I)V"); + return env->NewObject(simDeviceInfoCls, func, MakeJString(env, name), + (jint)handle); +} + +void DeviceInfo::CallJava(JNIEnv* env, jobject callobj) const { + env->CallVoidMethod(callobj, simDeviceCallbackCallback, + MakeJString(env, name), (jint)handle); +} + +std::pair ValueInfo::ToValue12() const { + jlong value1 = 0; + jdouble value2 = 0.0; + switch (value.type) { + case HAL_BOOLEAN: + value1 = value.data.v_boolean; + break; + case HAL_DOUBLE: + value2 = value.data.v_double; + break; + case HAL_ENUM: + value1 = value.data.v_enum; + break; + case HAL_INT: + value1 = value.data.v_int; + break; + case HAL_LONG: + value1 = value.data.v_long; + break; + default: + break; + } + return std::pair(value1, value2); +} + +jobject ValueInfo::MakeJava(JNIEnv* env) const { + static jmethodID func = + env->GetMethodID(simValueInfoCls, "", "(Ljava/lang/String;IZIJD)V"); + auto [value1, value2] = ToValue12(); + return env->NewObject(simValueInfoCls, func, MakeJString(env, name), + (jint)handle, (jboolean)readonly, (jint)value.type, + value1, value2); +} + +void ValueInfo::CallJava(JNIEnv* env, jobject callobj) const { + auto [value1, value2] = ToValue12(); + env->CallVoidMethod(callobj, simValueCallbackCallback, MakeJString(env, name), + (jint)handle, (jboolean)readonly, (jint)value.type, + value1, value2); +} + +namespace { + +class CallbackStore { + public: + explicit CallbackStore(JNIEnv* env, jobject obj) : m_call{env, obj} {} + ~CallbackStore() { + if (m_cancelCallback) m_cancelCallback(); + } + + void SetCancel(std::function cancelCallback) { + m_cancelCallback = std::move(cancelCallback); + } + void Free(JNIEnv* env) { m_call.free(env); } + jobject Get() const { return m_call; } + + private: + wpi::java::JGlobal m_call; + std::function m_cancelCallback; +}; + +class CallbackThreadJNI : public wpi::SafeThread { + public: + void Main(); + + using DeviceCalls = + std::vector, DeviceInfo>>; + DeviceCalls m_deviceCalls; + using ValueCalls = + std::vector, ValueInfo>>; + ValueCalls m_valueCalls; + + wpi::UidVector, 4> m_callbacks; +}; + +class CallbackJNI { + public: + static CallbackJNI& GetInstance() { + static CallbackJNI inst; + return inst; + } + void SendDevice(int32_t callback, DeviceInfo info); + void SendValue(int32_t callback, ValueInfo info); + + std::pair> AllocateCallback( + JNIEnv* env, jobject obj); + + void FreeCallback(JNIEnv* env, int32_t uid); + + private: + CallbackJNI() { m_owner.Start(); } + + wpi::SafeThreadOwner m_owner; +}; + +} // namespace + +void CallbackThreadJNI::Main() { + JNIEnv* env; + JavaVMAttachArgs args; + args.version = JNI_VERSION_1_2; + args.name = const_cast("SimDeviceCallback"); + args.group = nullptr; + jint rs = sim::GetJVM()->AttachCurrentThreadAsDaemon( + reinterpret_cast(&env), &args); + if (rs != JNI_OK) return; + + DeviceCalls deviceCalls; + ValueCalls valueCalls; + + std::unique_lock lock(m_mutex); + while (m_active) { + m_cond.wait(lock, [&] { return !m_active; }); + if (!m_active) break; + + deviceCalls.swap(m_deviceCalls); + valueCalls.swap(m_valueCalls); + + lock.unlock(); // don't hold mutex during callback execution + + for (auto&& call : deviceCalls) { + if (auto store = call.first.lock()) { + if (jobject callobj = store->Get()) { + call.second.CallJava(env, callobj); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } + } + } + + for (auto&& call : valueCalls) { + if (auto store = call.first.lock()) { + if (jobject callobj = store->Get()) { + call.second.CallJava(env, callobj); + if (env->ExceptionCheck()) { + env->ExceptionDescribe(); + env->ExceptionClear(); + } + } + } + } + + deviceCalls.clear(); + valueCalls.clear(); + + lock.lock(); + } + + // free global references + for (auto&& callback : m_callbacks) callback->Free(env); + + sim::GetJVM()->DetachCurrentThread(); +} + +void CallbackJNI::SendDevice(int32_t callback, DeviceInfo info) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_deviceCalls.emplace_back(thr->m_callbacks[callback], std::move(info)); + thr->m_cond.notify_one(); +} + +void CallbackJNI::SendValue(int32_t callback, ValueInfo info) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_valueCalls.emplace_back(thr->m_callbacks[callback], std::move(info)); + thr->m_cond.notify_one(); +} + +std::pair> +CallbackJNI::AllocateCallback(JNIEnv* env, jobject obj) { + auto thr = m_owner.GetThread(); + if (!thr) return std::pair(0, nullptr); + auto store = std::make_shared(env, obj); + return std::pair(thr->m_callbacks.emplace_back(store) + 1, store); +} + +void CallbackJNI::FreeCallback(JNIEnv* env, int32_t uid) { + auto thr = m_owner.GetThread(); + if (!thr) return; + if (uid <= 0 || static_cast(uid) >= thr->m_callbacks.size()) return; + --uid; + auto store = std::move(thr->m_callbacks[uid]); + thr->m_callbacks.erase(uid); + store->Free(env); +} + +namespace sim { + +bool InitializeSimDeviceDataJNI(JNIEnv* env) { + simDeviceInfoCls = JClass( + env, "edu/wpi/first/hal/sim/mockdata/SimDeviceDataJNI$SimDeviceInfo"); + if (!simDeviceInfoCls) return false; + + simValueInfoCls = JClass( + env, "edu/wpi/first/hal/sim/mockdata/SimDeviceDataJNI$SimValueInfo"); + if (!simValueInfoCls) return false; + + simDeviceCallbackCls = JClass(env, "edu/wpi/first/hal/sim/SimDeviceCallback"); + if (!simDeviceCallbackCls) return false; + + simDeviceCallbackCallback = env->GetMethodID(simDeviceCallbackCls, "callback", + "(Ljava/lang/String;I)V"); + if (!simDeviceCallbackCallback) return false; + + simValueCallbackCls = JClass(env, "edu/wpi/first/hal/sim/SimValueCallback"); + if (!simValueCallbackCls) return false; + + simValueCallbackCallback = env->GetMethodID( + simValueCallbackCls, "callbackNative", "(Ljava/lang/String;IZIJD)V"); + if (!simValueCallbackCallback) return false; + + return true; +} + +void FreeSimDeviceDataJNI(JNIEnv* env) { + simDeviceInfoCls.free(env); + simValueInfoCls.free(env); + simDeviceCallbackCls.free(env); + simValueCallbackCls.free(env); +} + +} // namespace sim + +extern "C" { + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: registerSimDeviceCreatedCallback + * Signature: (Ljava/lang/String;Ljava/lang/Object;Z)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_registerSimDeviceCreatedCallback + (JNIEnv* env, jclass, jstring prefix, jobject callback, + jboolean initialNotify) +{ + auto [uid, store] = + CallbackJNI::GetInstance().AllocateCallback(env, callback); + int32_t cuid = HALSIM_RegisterSimDeviceCreatedCallback( + JStringRef{env, prefix}.c_str(), + reinterpret_cast(static_cast(uid)), + [](const char* name, void* param, HAL_SimDeviceHandle handle) { + int32_t uid = reinterpret_cast(param); + CallbackJNI::GetInstance().SendDevice(uid, DeviceInfo{name, handle}); + }, + initialNotify); + store->SetCancel([cuid] { HALSIM_CancelSimDeviceCreatedCallback(cuid); }); + return uid; +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: cancelSimDeviceCreatedCallback + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_cancelSimDeviceCreatedCallback + (JNIEnv* env, jclass, jint uid) +{ + CallbackJNI::GetInstance().FreeCallback(env, uid); +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: registerSimDeviceFreedCallback + * Signature: (Ljava/lang/String;Ljava/lang/Object;)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_registerSimDeviceFreedCallback + (JNIEnv* env, jclass, jstring prefix, jobject callback) +{ + auto [uid, store] = + CallbackJNI::GetInstance().AllocateCallback(env, callback); + int32_t cuid = HALSIM_RegisterSimDeviceFreedCallback( + JStringRef{env, prefix}.c_str(), + reinterpret_cast(static_cast(uid)), + [](const char* name, void* param, HAL_SimDeviceHandle handle) { + int32_t uid = reinterpret_cast(param); + CallbackJNI::GetInstance().SendDevice(uid, DeviceInfo{name, handle}); + }); + store->SetCancel([cuid] { HALSIM_CancelSimDeviceFreedCallback(cuid); }); + return uid; +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: cancelSimDeviceFreedCallback + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_cancelSimDeviceFreedCallback + (JNIEnv* env, jclass, jint uid) +{ + CallbackJNI::GetInstance().FreeCallback(env, uid); +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: getSimDeviceHandle + * Signature: (Ljava/lang/String;)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_getSimDeviceHandle + (JNIEnv* env, jclass, jstring name) +{ + return HALSIM_GetSimDeviceHandle(JStringRef{env, name}.c_str()); +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: getSimValueDeviceHandle + * Signature: (I)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_getSimValueDeviceHandle + (JNIEnv*, jclass, jint handle) +{ + return HALSIM_GetSimValueDeviceHandle(handle); +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: enumerateSimDevices + * Signature: (Ljava/lang/String;)[Ljava/lang/Object; + */ +JNIEXPORT jobjectArray JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_enumerateSimDevices + (JNIEnv* env, jclass, jstring prefix) +{ + // get values + std::vector arr; + HALSIM_EnumerateSimDevices( + JStringRef{env, prefix}.c_str(), &arr, + [](const char* name, void* param, HAL_SimDeviceHandle handle) { + auto arr = static_cast*>(param); + arr->emplace_back(name, handle); + }); + + // convert to java + size_t numElems = arr.size(); + jobjectArray jarr = + env->NewObjectArray(arr.size(), simDeviceInfoCls, nullptr); + if (!jarr) return nullptr; + for (size_t i = 0; i < numElems; ++i) { + JLocal elem{env, arr[i].MakeJava(env)}; + env->SetObjectArrayElement(jarr, i, elem.obj()); + } + return jarr; +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: registerSimValueCreatedCallback + * Signature: (ILjava/lang/Object;Z)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_registerSimValueCreatedCallback + (JNIEnv* env, jclass, jint device, jobject callback, jboolean initialNotify) +{ + auto [uid, store] = + CallbackJNI::GetInstance().AllocateCallback(env, callback); + int32_t cuid = HALSIM_RegisterSimValueCreatedCallback( + device, reinterpret_cast(static_cast(uid)), + [](const char* name, void* param, HAL_SimValueHandle handle, + HAL_Bool readonly, const HAL_Value* value) { + int32_t uid = reinterpret_cast(param); + CallbackJNI::GetInstance().SendValue( + uid, ValueInfo{name, handle, static_cast(readonly), *value}); + }, + initialNotify); + store->SetCancel([cuid] { HALSIM_CancelSimValueCreatedCallback(cuid); }); + return uid; +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: cancelSimValueCreatedCallback + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_cancelSimValueCreatedCallback + (JNIEnv* env, jclass, jint uid) +{ + CallbackJNI::GetInstance().FreeCallback(env, uid); +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: registerSimValueChangedCallback + * Signature: (ILjava/lang/Object;Z)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_registerSimValueChangedCallback + (JNIEnv* env, jclass, jint handle, jobject callback, jboolean initialNotify) +{ + auto [uid, store] = + CallbackJNI::GetInstance().AllocateCallback(env, callback); + int32_t cuid = HALSIM_RegisterSimValueChangedCallback( + handle, reinterpret_cast(static_cast(uid)), + [](const char* name, void* param, HAL_SimValueHandle handle, + HAL_Bool readonly, const HAL_Value* value) { + int32_t uid = reinterpret_cast(param); + CallbackJNI::GetInstance().SendValue( + uid, ValueInfo{name, handle, static_cast(readonly), *value}); + }, + initialNotify); + store->SetCancel([cuid] { HALSIM_CancelSimValueChangedCallback(cuid); }); + return uid; +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: cancelSimValueChangedCallback + * Signature: (I)V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_cancelSimValueChangedCallback + (JNIEnv* env, jclass, jint uid) +{ + CallbackJNI::GetInstance().FreeCallback(env, uid); +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: getSimValueHandle + * Signature: (ILjava/lang/String;)I + */ +JNIEXPORT jint JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_getSimValueHandle + (JNIEnv* env, jclass, jint device, jstring name) +{ + return HALSIM_GetSimValueHandle(device, JStringRef{env, name}.c_str()); +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: enumerateSimValues + * Signature: (I)[Ljava/lang/Object; + */ +JNIEXPORT jobjectArray JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_enumerateSimValues + (JNIEnv* env, jclass, jint device) +{ + // get values + std::vector arr; + HALSIM_EnumerateSimValues( + device, &arr, + [](const char* name, void* param, HAL_SimValueHandle handle, + HAL_Bool readonly, const HAL_Value* value) { + auto arr = static_cast*>(param); + arr->emplace_back(name, handle, readonly, *value); + }); + + // convert to java + size_t numElems = arr.size(); + jobjectArray jarr = env->NewObjectArray(arr.size(), simValueInfoCls, nullptr); + if (!jarr) return nullptr; + for (size_t i = 0; i < numElems; ++i) { + JLocal elem{env, arr[i].MakeJava(env)}; + env->SetObjectArrayElement(jarr, i, elem.obj()); + } + return jarr; +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: getSimValueEnumOptions + * Signature: (I)[Ljava/lang/Object; + */ +JNIEXPORT jobjectArray JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_getSimValueEnumOptions + (JNIEnv* env, jclass, jint handle) +{ + static JClass stringCls{env, "java/lang/String"}; + if (!stringCls) return nullptr; + int32_t numElems = 0; + const char** elems = HALSIM_GetSimValueEnumOptions(handle, &numElems); + jobjectArray jarr = env->NewObjectArray(numElems, stringCls, nullptr); + if (!jarr) return nullptr; + for (int32_t i = 0; i < numElems; ++i) { + JLocal elem{env, MakeJString(env, elems[i])}; + env->SetObjectArrayElement(jarr, i, elem.obj()); + } + return jarr; +} + +/* + * Class: edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI + * Method: resetSimDeviceData + * Signature: ()V + */ +JNIEXPORT void JNICALL +Java_edu_wpi_first_hal_sim_mockdata_SimDeviceDataJNI_resetSimDeviceData + (JNIEnv*, jclass) +{ + HALSIM_ResetSimDeviceData(); +} + +} // extern "C" diff --git a/hal/src/main/native/sim/jni/SimDeviceDataJNI.h b/hal/src/main/native/sim/jni/SimDeviceDataJNI.h new file mode 100644 index 0000000000..56f6d9b322 --- /dev/null +++ b/hal/src/main/native/sim/jni/SimDeviceDataJNI.h @@ -0,0 +1,15 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +namespace sim { +bool InitializeSimDeviceDataJNI(JNIEnv* env); +void FreeSimDeviceDataJNI(JNIEnv* env); +} // namespace sim diff --git a/hal/src/main/native/sim/jni/SimulatorJNI.cpp b/hal/src/main/native/sim/jni/SimulatorJNI.cpp index 32b6110725..bba8f01521 100644 --- a/hal/src/main/native/sim/jni/SimulatorJNI.cpp +++ b/hal/src/main/native/sim/jni/SimulatorJNI.cpp @@ -7,9 +7,12 @@ #include "SimulatorJNI.h" +#include + #include "BufferCallbackStore.h" #include "CallbackStore.h" #include "ConstBufferCallbackStore.h" +#include "SimDeviceDataJNI.h" #include "SpiReadAutoReceiveBufferCallbackStore.h" #include "edu_wpi_first_hal_sim_mockdata_SimulatorJNI.h" #include "hal/HAL.h" @@ -19,7 +22,6 @@ using namespace wpi::java; static JavaVM* jvm = nullptr; -static JClass simValueCls; static JClass notifyCallbackCls; static JClass bufferCallbackCls; static JClass constBufferCallbackCls; @@ -37,9 +39,6 @@ jint SimOnLoad(JavaVM* vm, void* reserved) { if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) return JNI_ERR; - simValueCls = JClass(env, "edu/wpi/first/hal/sim/SimValue"); - if (!simValueCls) return JNI_ERR; - notifyCallbackCls = JClass(env, "edu/wpi/first/hal/sim/NotifyCallback"); if (!notifyCallbackCls) return JNI_ERR; @@ -75,6 +74,7 @@ jint SimOnLoad(JavaVM* vm, void* reserved) { InitializeBufferStore(); InitializeConstBufferStore(); InitializeSpiBufferStore(); + if (!InitializeSimDeviceDataJNI(env)) return JNI_ERR; return JNI_VERSION_1_6; } @@ -84,11 +84,11 @@ void SimOnUnload(JavaVM* vm, void* reserved) { if (vm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) != JNI_OK) return; - simValueCls.free(env); notifyCallbackCls.free(env); bufferCallbackCls.free(env); constBufferCallbackCls.free(env); spiReadAutoReceiveBufferCallbackCls.free(env); + FreeSimDeviceDataJNI(env); jvm = nullptr; } diff --git a/hal/src/main/native/sim/jni/SimulatorJNI.h b/hal/src/main/native/sim/jni/SimulatorJNI.h index de74a1cd32..8680396955 100644 --- a/hal/src/main/native/sim/jni/SimulatorJNI.h +++ b/hal/src/main/native/sim/jni/SimulatorJNI.h @@ -7,10 +7,7 @@ #pragma once -#include - #include "hal/Types.h" -#include "hal/Value.h" #include "jni.h" typedef HAL_Handle SIM_JniHandle; diff --git a/hal/src/main/native/sim/mockdata/SimDeviceData.cpp b/hal/src/main/native/sim/mockdata/SimDeviceData.cpp new file mode 100644 index 0000000000..61407ee37b --- /dev/null +++ b/hal/src/main/native/sim/mockdata/SimDeviceData.cpp @@ -0,0 +1,413 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "mockdata/SimDeviceData.h" // NOLINT(build/include_order) + +#include + +#include "SimDeviceDataInternal.h" + +using namespace hal; + +namespace hal { +namespace init { +void InitializeSimDeviceData() { + static SimDeviceData sdd; + ::hal::SimSimDeviceData = &sdd; +} +} // namespace init +} // namespace hal + +SimDeviceData* hal::SimSimDeviceData; + +SimDeviceData::Device* SimDeviceData::LookupDevice(HAL_SimDeviceHandle handle) { + if (handle <= 0) return nullptr; + --handle; + if (static_cast(handle) >= m_devices.size() || !m_devices[handle]) + return nullptr; + return m_devices[handle].get(); +} + +SimDeviceData::Value* SimDeviceData::LookupValue(HAL_SimValueHandle handle) { + if (handle <= 0) return nullptr; + + // look up device + Device* deviceImpl = LookupDevice(handle >> 16); + + // look up value + handle &= 0xffff; + --handle; + if (static_cast(handle) >= deviceImpl->values.size() || + !deviceImpl->values[handle]) + return nullptr; + + return deviceImpl->values[handle].get(); +} + +HAL_SimDeviceHandle SimDeviceData::CreateDevice(const char* name) { + std::scoped_lock lock(m_mutex); + + // check for duplicates and don't overwrite them + auto it = m_deviceMap.find(name); + if (it != m_deviceMap.end()) return 0; + + // don't allow more than 4096 devices (limit driven by 12-bit allocation in + // value changed callback uid) + if (m_devices.size() >= 4095) return 0; + + // create and save + auto deviceImpl = std::make_shared(name); + HAL_SimDeviceHandle deviceHandle = m_devices.emplace_back(deviceImpl) + 1; + deviceImpl->handle = deviceHandle; + m_deviceMap[name] = deviceImpl; + + // notify callbacks + m_deviceCreated(name, deviceHandle); + + return deviceHandle; +} + +void SimDeviceData::FreeDevice(HAL_SimDeviceHandle handle) { + std::scoped_lock lock(m_mutex); + --handle; + + // see if it exists + if (handle < 0 || static_cast(handle) >= m_devices.size()) return; + auto deviceImpl = std::move(m_devices[handle]); + if (!deviceImpl) return; + + // remove from map + m_deviceMap.erase(deviceImpl->name); + + // remove from vector + m_devices.erase(handle); + + // notify callbacks + m_deviceFreed(deviceImpl->name.c_str(), handle + 1); +} + +HAL_SimValueHandle SimDeviceData::CreateValue(HAL_SimDeviceHandle device, + const char* name, bool readonly, + int32_t numOptions, + const char** options, + const HAL_Value& initialValue) { + std::scoped_lock lock(m_mutex); + + // look up device + Device* deviceImpl = LookupDevice(device); + if (!deviceImpl) return 0; + + // check for duplicates and don't overwrite them + auto it = deviceImpl->valueMap.find(name); + if (it != deviceImpl->valueMap.end()) return 0; + + // don't allow more than 4096 values per device (limit driven by 12-bit + // allocation in value changed callback uid) + if (deviceImpl->values.size() >= 4095) return 0; + + // create and save; encode device into handle + auto valueImplPtr = std::make_unique(name, readonly, initialValue); + Value* valueImpl = valueImplPtr.get(); + HAL_SimValueHandle valueHandle = + (device << 16) | + (deviceImpl->values.emplace_back(std::move(valueImplPtr)) + 1); + valueImpl->handle = valueHandle; + // copy options (if any provided) + if (numOptions > 0 && options) { + valueImpl->enumOptions.reserve(numOptions); + valueImpl->cstrEnumOptions.reserve(numOptions); + for (int32_t i = 0; i < numOptions; ++i) { + valueImpl->enumOptions.emplace_back(options[i]); + // point to our copy of the string, not the passed-in one + valueImpl->cstrEnumOptions.emplace_back( + valueImpl->enumOptions.back().c_str()); + } + } + deviceImpl->valueMap[name] = valueImpl; + + // notify callbacks + deviceImpl->valueCreated(name, valueHandle, readonly, &initialValue); + + return valueHandle; +} + +HAL_Value SimDeviceData::GetValue(HAL_SimValueHandle handle) { + std::scoped_lock lock(m_mutex); + Value* valueImpl = LookupValue(handle); + + if (!valueImpl) { + HAL_Value value; + value.data.v_int = 0; + value.type = HAL_UNASSIGNED; + return value; + } + + return valueImpl->value; +} + +void SimDeviceData::SetValue(HAL_SimValueHandle handle, + const HAL_Value& value) { + std::scoped_lock lock(m_mutex); + Value* valueImpl = LookupValue(handle); + if (!valueImpl) return; + + valueImpl->value = value; + + // notify callbacks + valueImpl->changed(valueImpl->name.c_str(), valueImpl->handle, + valueImpl->readonly, &value); +} + +int32_t SimDeviceData::RegisterDeviceCreatedCallback( + const char* prefix, void* param, HALSIM_SimDeviceCallback callback, + bool initialNotify) { + std::scoped_lock lock(m_mutex); + + // register callback + int32_t index = m_deviceCreated.Register(prefix, param, callback); + + // initial notifications + if (initialNotify) { + for (auto&& device : m_devices) + callback(device->name.c_str(), param, device->handle); + } + + return index; +} + +void SimDeviceData::CancelDeviceCreatedCallback(int32_t uid) { + if (uid <= 0) return; + std::scoped_lock lock(m_mutex); + m_deviceCreated.Cancel(uid); +} + +int32_t SimDeviceData::RegisterDeviceFreedCallback( + const char* prefix, void* param, HALSIM_SimDeviceCallback callback) { + std::scoped_lock lock(m_mutex); + return m_deviceFreed.Register(prefix, param, callback); +} + +void SimDeviceData::CancelDeviceFreedCallback(int32_t uid) { + if (uid <= 0) return; + std::scoped_lock lock(m_mutex); + m_deviceFreed.Cancel(uid); +} + +HAL_SimDeviceHandle SimDeviceData::GetDeviceHandle(const char* name) { + std::scoped_lock lock(m_mutex); + auto it = m_deviceMap.find(name); + if (it == m_deviceMap.end()) return 0; + if (auto deviceImpl = it->getValue().lock()) + return deviceImpl->handle; + else + return 0; +} + +const char* SimDeviceData::GetDeviceName(HAL_SimDeviceHandle handle) { + std::scoped_lock lock(m_mutex); + + // look up device + Device* deviceImpl = LookupDevice(handle); + if (!deviceImpl) return nullptr; + + return deviceImpl->name.c_str(); +} + +void SimDeviceData::EnumerateDevices(const char* prefix, void* param, + HALSIM_SimDeviceCallback callback) { + std::scoped_lock lock(m_mutex); + for (auto&& device : m_devices) { + if (wpi::StringRef{device->name}.startswith(prefix)) + callback(device->name.c_str(), param, device->handle); + } +} + +int32_t SimDeviceData::RegisterValueCreatedCallback( + HAL_SimDeviceHandle device, void* param, HALSIM_SimValueCallback callback, + bool initialNotify) { + std::scoped_lock lock(m_mutex); + Device* deviceImpl = LookupDevice(device); + if (!deviceImpl) return -1; + + // register callback + int32_t index = deviceImpl->valueCreated.Register(callback, param); + + // initial notifications + if (initialNotify) { + for (auto&& value : deviceImpl->values) + callback(value->name.c_str(), param, value->handle, value->readonly, + &value->value); + } + + // encode device into uid + return (device << 16) | (index & 0xffff); +} + +void SimDeviceData::CancelValueCreatedCallback(int32_t uid) { + if (uid <= 0) return; + std::scoped_lock lock(m_mutex); + Device* deviceImpl = LookupDevice(uid >> 16); + if (!deviceImpl) return; + deviceImpl->valueCreated.Cancel(uid & 0xffff); +} + +int32_t SimDeviceData::RegisterValueChangedCallback( + HAL_SimValueHandle handle, void* param, HALSIM_SimValueCallback callback, + bool initialNotify) { + std::scoped_lock lock(m_mutex); + Value* valueImpl = LookupValue(handle); + if (!valueImpl) return -1; + + // register callback + int32_t index = valueImpl->changed.Register(callback, param); + + // initial notification + if (initialNotify) + callback(valueImpl->name.c_str(), param, valueImpl->handle, + valueImpl->readonly, &valueImpl->value); + + // encode device and value into uid + return (((handle >> 16) & 0xfff) << 19) | ((handle & 0xfff) << 7) | + (index & 0x7f); +} + +void SimDeviceData::CancelValueChangedCallback(int32_t uid) { + if (uid <= 0) return; + std::scoped_lock lock(m_mutex); + Value* valueImpl = LookupValue(((uid >> 19) << 16) | ((uid >> 7) & 0xfff)); + if (!valueImpl) return; + valueImpl->changed.Cancel(uid & 0x7f); +} + +HAL_SimValueHandle SimDeviceData::GetValueHandle(HAL_SimDeviceHandle device, + const char* name) { + std::scoped_lock lock(m_mutex); + Device* deviceImpl = LookupDevice(device); + if (!deviceImpl) return 0; + + // lookup value + auto it = deviceImpl->valueMap.find(name); + if (it == deviceImpl->valueMap.end()) return 0; + if (!it->getValue()) return 0; + return it->getValue()->handle; +} + +void SimDeviceData::EnumerateValues(HAL_SimDeviceHandle device, void* param, + HALSIM_SimValueCallback callback) { + std::scoped_lock lock(m_mutex); + Device* deviceImpl = LookupDevice(device); + if (!deviceImpl) return; + + for (auto&& value : deviceImpl->values) + callback(value->name.c_str(), param, value->handle, value->readonly, + &value->value); +} + +const char** SimDeviceData::GetValueEnumOptions(HAL_SimValueHandle handle, + int32_t* numOptions) { + *numOptions = 0; + + std::scoped_lock lock(m_mutex); + Value* valueImpl = LookupValue(handle); + if (!valueImpl) return nullptr; + + // get list of options (safe to return as they never change) + auto& options = valueImpl->cstrEnumOptions; + *numOptions = options.size(); + return options.data(); +} + +void SimDeviceData::ResetData() { + std::scoped_lock lock(m_mutex); + m_devices.clear(); + m_deviceMap.clear(); + m_deviceCreated.Reset(); + m_deviceFreed.Reset(); +} + +extern "C" { + +int32_t HALSIM_RegisterSimDeviceCreatedCallback( + const char* prefix, void* param, HALSIM_SimDeviceCallback callback, + HAL_Bool initialNotify) { + return SimSimDeviceData->RegisterDeviceCreatedCallback( + prefix, param, callback, initialNotify); +} + +void HALSIM_CancelSimDeviceCreatedCallback(int32_t uid) { + SimSimDeviceData->CancelDeviceCreatedCallback(uid); +} + +int32_t HALSIM_RegisterSimDeviceFreedCallback( + const char* prefix, void* param, HALSIM_SimDeviceCallback callback) { + return SimSimDeviceData->RegisterDeviceFreedCallback(prefix, param, callback); +} + +void HALSIM_CancelSimDeviceFreedCallback(int32_t uid) { + SimSimDeviceData->CancelDeviceFreedCallback(uid); +} + +HAL_SimDeviceHandle HALSIM_GetSimDeviceHandle(const char* name) { + return SimSimDeviceData->GetDeviceHandle(name); +} + +const char* HALSIM_GetSimDeviceName(HAL_SimDeviceHandle handle) { + return SimSimDeviceData->GetDeviceName(handle); +} + +HAL_SimDeviceHandle HALSIM_GetSimValueDeviceHandle(HAL_SimValueHandle handle) { + if (handle <= 0) return 0; + return handle >> 16; +} + +void HALSIM_EnumerateSimDevices(const char* prefix, void* param, + HALSIM_SimDeviceCallback callback) { + SimSimDeviceData->EnumerateDevices(prefix, param, callback); +} + +int32_t HALSIM_RegisterSimValueCreatedCallback(HAL_SimDeviceHandle device, + void* param, + HALSIM_SimValueCallback callback, + HAL_Bool initialNotify) { + return SimSimDeviceData->RegisterValueCreatedCallback(device, param, callback, + initialNotify); +} + +void HALSIM_CancelSimValueCreatedCallback(int32_t uid) { + SimSimDeviceData->CancelValueCreatedCallback(uid); +} + +int32_t HALSIM_RegisterSimValueChangedCallback(HAL_SimValueHandle handle, + void* param, + HALSIM_SimValueCallback callback, + HAL_Bool initialNotify) { + return SimSimDeviceData->RegisterValueChangedCallback(handle, param, callback, + initialNotify); +} + +void HALSIM_CancelSimValueChangedCallback(int32_t uid) { + SimSimDeviceData->CancelValueChangedCallback(uid); +} + +HAL_SimValueHandle HALSIM_GetSimValueHandle(HAL_SimDeviceHandle device, + const char* name) { + return SimSimDeviceData->GetValueHandle(device, name); +} + +void HALSIM_EnumerateSimValues(HAL_SimDeviceHandle device, void* param, + HALSIM_SimValueCallback callback) { + SimSimDeviceData->EnumerateValues(device, param, callback); +} + +const char** HALSIM_GetSimValueEnumOptions(HAL_SimValueHandle handle, + int32_t* numOptions) { + return SimSimDeviceData->GetValueEnumOptions(handle, numOptions); +} + +void HALSIM_ResetSimDeviceData(void) { SimSimDeviceData->ResetData(); } + +} // extern "C" diff --git a/hal/src/main/native/sim/mockdata/SimDeviceDataInternal.h b/hal/src/main/native/sim/mockdata/SimDeviceDataInternal.h new file mode 100644 index 0000000000..2582eca3ac --- /dev/null +++ b/hal/src/main/native/sim/mockdata/SimDeviceDataInternal.h @@ -0,0 +1,214 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) 2019 FIRST. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#pragma once + +#include + +#include +#include +#include +#include + +#include +#include +#include + +#include "hal/Value.h" +#include "mockdata/SimCallbackRegistry.h" +#include "mockdata/SimDeviceData.h" + +namespace hal { + +namespace impl { + +template +class SimUnnamedCallbackRegistry { + public: + using RawFunctor = void (*)(); + + protected: + using CallbackVector = wpi::UidVector, 4>; + + public: + void Cancel(int32_t uid) { + if (m_callbacks) m_callbacks->erase(uid - 1); + } + + void Reset() { + if (m_callbacks) m_callbacks->clear(); + } + + int32_t Register(CallbackFunction callback, void* param) { + // Must return -1 on a null callback for error handling + if (callback == nullptr) return -1; + if (!m_callbacks) m_callbacks = std::make_unique(); + return m_callbacks->emplace_back(param, + reinterpret_cast(callback)) + + 1; + } + + template + void Invoke(const char* name, U&&... u) const { + if (m_callbacks) { + for (auto&& cb : *m_callbacks) { + reinterpret_cast(cb.callback)(name, cb.param, + std::forward(u)...); + } + } + } + + template + LLVM_ATTRIBUTE_ALWAYS_INLINE void operator()(U&&... u) const { + return Invoke(std::forward(u)...); + } + + private: + std::unique_ptr m_callbacks; +}; + +template +class SimPrefixCallbackRegistry { + struct CallbackData { + CallbackData() = default; + CallbackData(const char* prefix_, void* param_, CallbackFunction callback_) + : prefix{prefix_}, callback{callback_}, param{param_} {} + std::string prefix; + CallbackFunction callback; + void* param; + + explicit operator bool() const { return callback != nullptr; } + }; + using CallbackVector = wpi::UidVector; + + public: + void Cancel(int32_t uid) { + if (m_callbacks) m_callbacks->erase(uid - 1); + } + + void Reset() { + if (m_callbacks) m_callbacks->clear(); + } + + int32_t Register(const char* prefix, void* param, CallbackFunction callback) { + // Must return -1 on a null callback for error handling + if (callback == nullptr) return -1; + if (!m_callbacks) m_callbacks = std::make_unique(); + return m_callbacks->emplace_back(prefix, param, callback) + 1; + } + + template + void Invoke(const char* name, U&&... u) const { + if (m_callbacks) { + for (auto&& cb : *m_callbacks) { + if (wpi::StringRef{name}.startswith(cb.prefix)) { + cb.callback(name, cb.param, std::forward(u)...); + } + } + } + } + + template + LLVM_ATTRIBUTE_ALWAYS_INLINE void operator()(U&&... u) const { + return Invoke(std::forward(u)...); + } + + private: + std::unique_ptr m_callbacks; +}; + +} // namespace impl + +class SimDeviceData { + private: + struct Value { + Value(const char* name_, bool readonly_, const HAL_Value& value_) + : name{name_}, readonly{readonly_}, value{value_} {} + + HAL_SimValueHandle handle{0}; + std::string name; + bool readonly; + HAL_Value value; + std::vector enumOptions; + std::vector cstrEnumOptions; + impl::SimUnnamedCallbackRegistry changed; + }; + + struct Device { + explicit Device(const char* name_) : name{name_} {} + + HAL_SimDeviceHandle handle{0}; + std::string name; + wpi::UidVector, 16> values; + wpi::StringMap valueMap; + impl::SimUnnamedCallbackRegistry valueCreated; + }; + + wpi::UidVector, 4> m_devices; + wpi::StringMap> m_deviceMap; + + wpi::recursive_spinlock m_mutex; + + impl::SimPrefixCallbackRegistry m_deviceCreated; + impl::SimPrefixCallbackRegistry m_deviceFreed; + + // call with lock held, returns null if does not exist + Device* LookupDevice(HAL_SimDeviceHandle handle); + Value* LookupValue(HAL_SimValueHandle handle); + + public: + HAL_SimDeviceHandle CreateDevice(const char* name); + void FreeDevice(HAL_SimDeviceHandle handle); + HAL_SimValueHandle CreateValue(HAL_SimDeviceHandle device, const char* name, + bool readonly, int32_t numOptions, + const char** options, + const HAL_Value& initialValue); + HAL_Value GetValue(HAL_SimValueHandle handle); + void SetValue(HAL_SimValueHandle handle, const HAL_Value& value); + + int32_t RegisterDeviceCreatedCallback(const char* prefix, void* param, + HALSIM_SimDeviceCallback callback, + bool initialNotify); + + void CancelDeviceCreatedCallback(int32_t uid); + + int32_t RegisterDeviceFreedCallback(const char* prefix, void* param, + HALSIM_SimDeviceCallback callback); + + void CancelDeviceFreedCallback(int32_t uid); + + HAL_SimDeviceHandle GetDeviceHandle(const char* name); + const char* GetDeviceName(HAL_SimDeviceHandle handle); + + void EnumerateDevices(const char* prefix, void* param, + HALSIM_SimDeviceCallback callback); + + int32_t RegisterValueCreatedCallback(HAL_SimDeviceHandle device, void* param, + HALSIM_SimValueCallback callback, + bool initialNotify); + + void CancelValueCreatedCallback(int32_t uid); + + int32_t RegisterValueChangedCallback(HAL_SimValueHandle handle, void* param, + HALSIM_SimValueCallback callback, + bool initialNotify); + + void CancelValueChangedCallback(int32_t uid); + + HAL_SimValueHandle GetValueHandle(HAL_SimDeviceHandle device, + const char* name); + + void EnumerateValues(HAL_SimDeviceHandle device, void* param, + HALSIM_SimValueCallback callback); + + const char** GetValueEnumOptions(HAL_SimValueHandle handle, + int32_t* numOptions); + + void ResetData(); +}; +extern SimDeviceData* SimSimDeviceData; +} // namespace hal