diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/NetworkSendableTable.java b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkSendableTable.java new file mode 100644 index 00000000000..56432ddba8b --- /dev/null +++ b/ntcore/src/main/java/edu/wpi/first/networktables/NetworkSendableTable.java @@ -0,0 +1,1204 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.networktables; + +import edu.wpi.first.util.function.BooleanConsumer; +import edu.wpi.first.util.function.FloatConsumer; +import edu.wpi.first.util.function.FloatSupplier; +import edu.wpi.first.util.protobuf.Protobuf; +import edu.wpi.first.util.protobuf.ProtobufBuffer; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableOption; +import edu.wpi.first.util.sendable2.SendableTable; +import edu.wpi.first.util.struct.Struct; +import edu.wpi.first.util.struct.StructBuffer; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.util.ArrayList; +import java.util.EnumSet; +import java.util.List; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleSupplier; +import java.util.function.LongConsumer; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +/** NetworkTables-backed implementation of SendableTable. */ +public class NetworkSendableTable implements SendableTable { + private static final char PATH_SEPARATOR = '/'; + + private final String m_path; + private final String m_pathWithSep; + private final NetworkTableInstance m_inst; + + private static class TopicData { + TopicData(Topic topic) { + m_topic = topic; + } + + synchronized void close() { + if (m_publisher != null) { + m_publisher.close(); + } + if (m_subscriber != null) { + m_subscriber.close(); + } + for (Integer listener : m_listeners) { + m_topic.m_inst.removeListener(listener); + } + } + + synchronized GenericPublisher publish(String typeString) { + if (m_publisher != null) { + return m_publisher; + } + if (m_typeString == null) { + m_typeString = typeString; + } + m_publisher = m_topic.genericPublish(m_typeString, m_pubOptions); + return m_publisher; + } + + synchronized GenericSubscriber subscribe(String typeString) { + if (m_subscriber != null) { + return m_subscriber; + } + if (m_typeString == null) { + m_typeString = typeString; + } + m_subscriber = m_topic.genericSubscribe(m_typeString, m_subOptions); + return m_subscriber; + } + + synchronized GenericPublisher getPublisher() { + if (m_publisher != null && m_publisher.isValid()) { + return m_publisher; + } else { + return null; + } + } + + private PubSubOption[] fromSendableOptions(SendableOption... options) { + ArrayList out = new ArrayList<>(); + for (SendableOption option : options) { + switch (option.getKind()) { + case kPeriodic: + out.add(PubSubOption.periodic(option.getDoubleValue())); + break; + case kTypeString: + m_typeString = option.getStringValue(); + break; + case kSendAll: + out.add(PubSubOption.sendAll(option.getBooleanValue())); + break; + case kPollStorage: + out.add(PubSubOption.pollStorage(option.getIntValue())); + break; + case kKeepDuplicates: + out.add(PubSubOption.keepDuplicates(option.getBooleanValue())); + break; + case kDisableRemote: + out.add(PubSubOption.disableRemote(option.getBooleanValue())); + break; + case kDisableLocal: + out.add(PubSubOption.disableLocal(option.getBooleanValue())); + break; + default: + break; + } + } + return out.toArray(new PubSubOption[0]); + } + + synchronized void setPublishOptions(SendableOption... options) { + m_pubOptions = fromSendableOptions(options); + if (m_publisher != null) { + m_publisher.close(); + m_publisher = m_topic.genericPublish(m_typeString, m_pubOptions); + } + } + + synchronized void setSubscribeOptions(SendableOption... options) { + m_pubOptions = fromSendableOptions(options); + if (m_subscriber != null) { + m_subscriber.close(); + m_subscriber = m_topic.genericSubscribe(m_typeString, m_pubOptions); + } + } + + synchronized void addValueListener(String typeString, Consumer cb) { + if (m_subscriber == null) { + subscribe(typeString); + } + m_listeners.add( + m_topic + .getInstance() + .addListener( + m_subscriber, + EnumSet.of(NetworkTableEvent.Kind.kValueAll), + e -> { + if (e.valueData != null) { + cb.accept(e.valueData.value); + } + })); + } + + void setPolledUpdate(Consumer consumer) { + m_polledUpdate.set(consumer); + } + + Consumer getPolledUpdate() { + return m_polledUpdate.get(); + } + + final Topic m_topic; + GenericPublisher m_publisher; + GenericSubscriber m_subscriber; + String m_typeString; + PubSubOption[] m_pubOptions = new PubSubOption[0]; + PubSubOption[] m_subOptions = new PubSubOption[0]; + StructBuffer m_structBuffer; + ProtobufBuffer m_protobufBuffer; + final AtomicReference> m_polledUpdate = new AtomicReference<>(); + final List m_listeners = new ArrayList<>(); + } + + private final ConcurrentMap m_topics = new ConcurrentHashMap<>(); + private final ConcurrentMap m_tables = new ConcurrentHashMap<>(); + private Consumer m_closeSendable; + private boolean m_closed; + + private TopicData getTopicData(String name) { + return m_topics.computeIfAbsent( + name, k -> new TopicData(m_inst.getTopic(m_pathWithSep + name))); + } + + private TopicData publish(String name, String typeString) { + TopicData data = getTopicData(name); + data.publish(typeString); + return data; + } + + /** + * Construct a NetworkTables-backed SendableTable. + * + * @param inst NetworkTables instance + * @param path NetworkTables path for table root + */ + public NetworkSendableTable(NetworkTableInstance inst, String path) { + m_path = path; + m_pathWithSep = path + PATH_SEPARATOR; + m_inst = inst; + } + + /** + * Gets the instance for the table. + * + * @return Instance + */ + public NetworkTableInstance getInstance() { + return m_inst; + } + + @Override + public String toString() { + return "NetworkSendableTable: " + m_path; + } + + @Override + public boolean getBoolean(String name, boolean defaultValue) { + return getTopicData(name).subscribe("boolean").getBoolean(defaultValue); + } + + @Override + public long getInteger(String name, long defaultValue) { + return getTopicData(name).subscribe("int").getInteger(defaultValue); + } + + @Override + public float getFloat(String name, float defaultValue) { + return getTopicData(name).subscribe("float").getFloat(defaultValue); + } + + @Override + public double getDouble(String name, double defaultValue) { + return getTopicData(name).subscribe("double").getDouble(defaultValue); + } + + @Override + public String getString(String name, String defaultValue) { + return getTopicData(name).subscribe("string").getString(defaultValue); + } + + @Override + public boolean[] getBooleanArray(String name, boolean[] defaultValue) { + return getTopicData(name).subscribe("boolean[]").getBooleanArray(defaultValue); + } + + @Override + public long[] getIntegerArray(String name, long[] defaultValue) { + return getTopicData(name).subscribe("int[]").getIntegerArray(defaultValue); + } + + @Override + public float[] getFloatArray(String name, float[] defaultValue) { + return getTopicData(name).subscribe("float[]").getFloatArray(defaultValue); + } + + @Override + public double[] getDoubleArray(String name, double[] defaultValue) { + return getTopicData(name).subscribe("double[]").getDoubleArray(defaultValue); + } + + @Override + public String[] getStringArray(String name, String[] defaultValue) { + return getTopicData(name).subscribe("string[]").getStringArray(defaultValue); + } + + @Override + public byte[] getRaw(String name, String typeString, byte[] defaultValue) { + return getTopicData(name).subscribe(typeString).getRaw(defaultValue); + } + + @Override + public T getStruct(String name, Struct struct, T defaultValue) { + TopicData td = getTopicData(name); + byte[] data = td.subscribe(struct.getTypeString()).getRaw(null); + if (data == null) { + return defaultValue; + } + if (td.m_structBuffer == null) { + td.m_structBuffer = StructBuffer.create(struct); + } else if (!td.m_structBuffer.getStruct().equals(struct)) { + return defaultValue; + } + @SuppressWarnings("unchecked") + StructBuffer buf = (StructBuffer) td.m_structBuffer; + return buf.read(data); + } + + @Override + public boolean getStructInto(String name, T out, Struct struct) { + TopicData td = getTopicData(name); + synchronized (td) { + if (td.m_subscriber == null) { + td.subscribe(struct.getTypeString()); + addSchema(struct); + } + byte[] data = td.m_subscriber.getRaw(null); + if (data == null) { + return false; + } + if (td.m_structBuffer == null) { + td.m_structBuffer = StructBuffer.create(struct); + } else if (!td.m_structBuffer.getStruct().equals(struct)) { + return false; + } + @SuppressWarnings("unchecked") + StructBuffer buf = (StructBuffer) td.m_structBuffer; + buf.readInto(out, data); + return true; + } + } + + @Override + public T getProtobuf(String name, Protobuf proto, T defaultValue) { + TopicData td = getTopicData(name); + synchronized (td) { + if (td.m_subscriber == null) { + td.subscribe(proto.getTypeString()); + addSchema(proto); + } + byte[] data = td.m_subscriber.getRaw(null); + if (data == null) { + return defaultValue; + } + if (td.m_protobufBuffer == null) { + td.m_protobufBuffer = ProtobufBuffer.create(proto); + } else if (!td.m_protobufBuffer.getProto().equals(proto)) { + return defaultValue; + } + @SuppressWarnings("unchecked") + ProtobufBuffer buf = (ProtobufBuffer) td.m_protobufBuffer; + try { + return buf.read(data); + } catch (IOException e) { + return defaultValue; + } + } + } + + @Override + public boolean getProtobufInto(String name, T out, Protobuf proto) { + TopicData td = getTopicData(name); + byte[] data = td.subscribe(proto.getTypeString()).getRaw(null); + if (data == null) { + return false; + } + if (td.m_protobufBuffer == null) { + td.m_protobufBuffer = ProtobufBuffer.create(proto); + } else if (!td.m_protobufBuffer.getProto().equals(proto)) { + return false; + } + @SuppressWarnings("unchecked") + ProtobufBuffer buf = (ProtobufBuffer) td.m_protobufBuffer; + try { + buf.readInto(out, data); + } catch (IOException e) { + return false; + } + return true; + } + + @Override + public void setBoolean(String name, boolean value) { + getTopicData(name).publish("boolean").setBoolean(value); + } + + @Override + public void setInteger(String name, long value) { + getTopicData(name).publish("int").setInteger(value); + } + + @Override + public void setFloat(String name, float value) { + getTopicData(name).publish("float").setFloat(value); + } + + @Override + public void setDouble(String name, double value) { + getTopicData(name).publish("double").setDouble(value); + } + + @Override + public void setString(String name, String value) { + getTopicData(name).publish("string").setString(value); + } + + @Override + public void setBooleanArray(String name, boolean[] value) { + getTopicData(name).publish("boolean[]").setBooleanArray(value); + } + + @Override + public void setIntegerArray(String name, long[] value) { + getTopicData(name).publish("int[]").setIntegerArray(value); + } + + @Override + public void setFloatArray(String name, float[] value) { + getTopicData(name).publish("float[]").setFloatArray(value); + } + + @Override + public void setDoubleArray(String name, double[] value) { + getTopicData(name).publish("double[]").setDoubleArray(value); + } + + @Override + public void setStringArray(String name, String[] value) { + getTopicData(name).publish("string[]").setStringArray(value); + } + + @Override + public void setRaw(String name, String typeString, byte[] value, int start, int len) { + getTopicData(name).publish(typeString).setRaw(value, start, len); + } + + @Override + public void setRaw(String name, String typeString, ByteBuffer value, int start, int len) { + getTopicData(name).publish(typeString).setRaw(value, start, len); + } + + @Override + public void setStruct(String name, T value, Struct struct) { + TopicData td = getTopicData(name); + synchronized (td) { + if (td.m_publisher == null) { + td.publish(struct.getTypeString()); + addSchema(struct); + } + if (td.m_structBuffer == null) { + td.m_structBuffer = StructBuffer.create(struct); + } else if (!td.m_structBuffer.getStruct().equals(struct)) { + return; + } + @SuppressWarnings("unchecked") + StructBuffer buf = (StructBuffer) td.m_structBuffer; + ByteBuffer bb = buf.write(value); + td.m_publisher.setRaw(bb, 0, bb.position()); + } + } + + @Override + public void setProtobuf(String name, T value, Protobuf proto) { + TopicData td = getTopicData(name); + synchronized (td) { + if (td.m_publisher == null) { + td.publish(proto.getTypeString()); + addSchema(proto); + } + if (td.m_protobufBuffer == null) { + td.m_protobufBuffer = ProtobufBuffer.create(proto); + } else if (!td.m_protobufBuffer.getProto().equals(proto)) { + return; + } + @SuppressWarnings("unchecked") + ProtobufBuffer buf = (ProtobufBuffer) td.m_protobufBuffer; + try { + ByteBuffer bb = buf.write(value); + td.m_publisher.setRaw(bb, 0, bb.position()); + } catch (IOException e) { + // ignore + } + } + } + + @Override + public void publishBoolean(String name, BooleanSupplier supplier) { + TopicData data = publish(name, "boolean"); + data.setPolledUpdate( + pub -> { + pub.setBoolean(supplier.getAsBoolean()); + }); + } + + @Override + public void publishInteger(String name, LongSupplier supplier) { + TopicData data = publish(name, "int"); + data.setPolledUpdate( + pub -> { + pub.setInteger(supplier.getAsLong()); + }); + } + + @Override + public void publishFloat(String name, FloatSupplier supplier) { + TopicData data = publish(name, "float"); + data.setPolledUpdate( + pub -> { + pub.setFloat(supplier.getAsFloat()); + }); + } + + @Override + public void publishDouble(String name, DoubleSupplier supplier) { + TopicData data = publish(name, "double"); + data.setPolledUpdate( + pub -> { + pub.setDouble(supplier.getAsDouble()); + }); + } + + @Override + public void publishString(String name, Supplier supplier) { + TopicData data = publish(name, "string"); + data.setPolledUpdate( + pub -> { + pub.setString(supplier.get()); + }); + } + + @Override + public void publishBooleanArray(String name, Supplier supplier) { + TopicData data = publish(name, "boolean[]"); + data.setPolledUpdate( + pub -> { + pub.setBooleanArray(supplier.get()); + }); + } + + @Override + public void publishIntegerArray(String name, Supplier supplier) { + TopicData data = publish(name, "int[]"); + data.setPolledUpdate( + pub -> { + pub.setIntegerArray(supplier.get()); + }); + } + + @Override + public void publishFloatArray(String name, Supplier supplier) { + TopicData data = publish(name, "float[]"); + data.setPolledUpdate( + pub -> { + pub.setFloatArray(supplier.get()); + }); + } + + @Override + public void publishDoubleArray(String name, Supplier supplier) { + TopicData data = publish(name, "double[]"); + data.setPolledUpdate( + pub -> { + pub.setDoubleArray(supplier.get()); + }); + } + + @Override + public void publishStringArray(String name, Supplier supplier) { + TopicData data = publish(name, "string[]"); + data.setPolledUpdate( + pub -> { + pub.setStringArray(supplier.get()); + }); + } + + @Override + public void publishRawBytes(String name, String typeString, Supplier supplier) { + TopicData data = publish(name, typeString); + data.setPolledUpdate( + pub -> { + pub.setRaw(supplier.get()); + }); + } + + @Override + public void publishRawBuffer(String name, String typeString, Supplier supplier) { + TopicData data = publish(name, typeString); + data.setPolledUpdate( + pub -> { + pub.setRaw(supplier.get()); + }); + } + + @Override + public void publishStruct(String name, Struct struct, Supplier supplier) { + TopicData td = getTopicData(name); + td.publish(struct.getTypeString()); + addSchema(struct); + final StructBuffer buf = StructBuffer.create(struct); + td.setPolledUpdate( + pub -> { + pub.setRaw(buf.write(supplier.get())); + }); + } + + @Override + public void publishProtobuf(String name, Protobuf proto, Supplier supplier) { + TopicData td = getTopicData(name); + td.publish(proto.getTypeString()); + addSchema(proto); + final ProtobufBuffer buf = ProtobufBuffer.create(proto); + td.setPolledUpdate( + pub -> { + try { + pub.setRaw(buf.write(supplier.get())); + } catch (IOException e) { + return; // ignore + } + }); + } + + @Override + public BooleanConsumer addBooleanPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "boolean")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setBoolean(value); + } + }; + } + + @Override + public LongConsumer addIntegerPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "int")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setInteger(value); + } + }; + } + + @Override + public FloatConsumer addFloatPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "float")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setFloat(value); + } + }; + } + + @Override + public DoubleConsumer addDoublePublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "double")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setDouble(value); + } + }; + } + + @Override + public Consumer addStringPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "string")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setString(value); + } + }; + } + + @Override + public Consumer addBooleanArrayPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "boolean[]")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setBooleanArray(value); + } + }; + } + + @Override + public Consumer addIntegerArrayPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "int[]")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setIntegerArray(value); + } + }; + } + + @Override + public Consumer addFloatArrayPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "float[]")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setFloatArray(value); + } + }; + } + + @Override + public Consumer addDoubleArrayPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "double[]")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setDoubleArray(value); + } + }; + } + + @Override + public Consumer addStringArrayPublisher(String name) { + final WeakReference dataRef = new WeakReference<>(publish(name, "string[]")); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setStringArray(value); + } + }; + } + + @Override + public Consumer addRawBytesPublisher(String name, String typeString) { + final WeakReference dataRef = new WeakReference<>(publish(name, typeString)); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setRaw(value); + } + }; + } + + @Override + public Consumer addRawBufferPublisher(String name, String typeString) { + final WeakReference dataRef = new WeakReference<>(publish(name, typeString)); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setRaw(value); + } + }; + } + + @Override + public Consumer addStructPublisher(String name, Struct struct) { + addSchema(struct); + final StructBuffer buf = StructBuffer.create(struct); + final WeakReference dataRef = + new WeakReference<>(publish(name, struct.getTypeString())); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + publisher.setRaw(buf.write(value)); + } + }; + } + + @Override + public Consumer addProtobufPublisher(String name, Protobuf proto) { + addSchema(proto); + final ProtobufBuffer buf = ProtobufBuffer.create(proto); + final WeakReference dataRef = + new WeakReference<>(publish(name, proto.getTypeString())); + return value -> { + TopicData data = dataRef.get(); + if (data == null) { + return; + } + GenericPublisher publisher = data.getPublisher(); + if (publisher != null) { + try { + publisher.setRaw(buf.write(value)); + } catch (IOException e) { + // ignore + } + } + }; + } + + @Override + public void subscribeBoolean(String name, BooleanConsumer consumer) { + getTopicData(name) + .addValueListener( + "boolean", + value -> { + if (value.isBoolean()) { + consumer.accept(value.getBoolean()); + } + }); + } + + @Override + public void subscribeInteger(String name, LongConsumer consumer) { + getTopicData(name) + .addValueListener( + "int", + value -> { + if (value.isInteger()) { + consumer.accept(value.getInteger()); + } + }); + } + + @Override + public void subscribeFloat(String name, FloatConsumer consumer) { + getTopicData(name) + .addValueListener( + "float", + value -> { + if (value.isFloat()) { + consumer.accept(value.getFloat()); + } + }); + } + + @Override + public void subscribeDouble(String name, DoubleConsumer consumer) { + getTopicData(name) + .addValueListener( + "double", + value -> { + if (value.isDouble()) { + consumer.accept(value.getDouble()); + } + }); + } + + @Override + public void subscribeString(String name, Consumer consumer) { + getTopicData(name) + .addValueListener( + "string", + value -> { + if (value.isString()) { + consumer.accept(value.getString()); + } + }); + } + + @Override + public void subscribeBooleanArray(String name, Consumer consumer) { + getTopicData(name) + .addValueListener( + "boolean[]", + value -> { + if (value.isBooleanArray()) { + consumer.accept(value.getBooleanArray()); + } + }); + } + + @Override + public void subscribeIntegerArray(String name, Consumer consumer) { + getTopicData(name) + .addValueListener( + "int[]", + value -> { + if (value.isIntegerArray()) { + consumer.accept(value.getIntegerArray()); + } + }); + } + + @Override + public void subscribeFloatArray(String name, Consumer consumer) { + getTopicData(name) + .addValueListener( + "float[]", + value -> { + if (value.isFloatArray()) { + consumer.accept(value.getFloatArray()); + } + }); + } + + @Override + public void subscribeDoubleArray(String name, Consumer consumer) { + getTopicData(name) + .addValueListener( + "double[]", + value -> { + if (value.isDoubleArray()) { + consumer.accept(value.getDoubleArray()); + } + }); + } + + @Override + public void subscribeStringArray(String name, Consumer consumer) { + getTopicData(name) + .addValueListener( + "string[]", + value -> { + if (value.isStringArray()) { + consumer.accept(value.getStringArray()); + } + }); + } + + @Override + public void subscribeRawBytes(String name, String typeString, Consumer consumer) { + getTopicData(name) + .addValueListener( + typeString, + value -> { + if (value.isRaw()) { + consumer.accept(value.getRaw()); + } + }); + } + + @Override + public NetworkSendableTable addSendable(String name, T obj, Sendable sendable) { + NetworkSendableTable child = getChild(name); + if (child.m_closeSendable == null) { + sendable.initSendable(obj, child); + child.m_closeSendable = + table -> { + sendable.closeSendable(obj, table); + }; + } + return child; + } + + @SuppressWarnings("resource") + @Override + public NetworkSendableTable getChild(String name) { + return m_tables.computeIfAbsent( + name, k -> new NetworkSendableTable(m_inst, m_pathWithSep + name)); + } + + @Override + public void setPublishOptions(String name, SendableOption... options) { + getTopicData(name).setPublishOptions(options); + } + + @Override + public void setSubscribeOptions(String name, SendableOption... options) { + getTopicData(name).setSubscribeOptions(options); + } + + /** + * Gets the current value of a property (as a JSON string). + * + * @param name name + * @param propName property name + * @return JSON string; "null" if the property does not exist. + */ + @Override + public String getProperty(String name, String propName) { + return getTopicData(name).m_topic.getProperty(propName); + } + + /** + * Sets a property value. + * + * @param name name + * @param propName property name + * @param value property value (JSON string) + * @throws IllegalArgumentException if properties is not parseable as JSON + */ + @Override + public void setProperty(String name, String propName, String value) { + getTopicData(name).m_topic.setProperty(propName, value); + } + + /** + * Deletes a property. Has no effect if the property does not exist. + * + * @param name name + * @param propName property name + */ + @Override + public void deleteProperty(String name, String propName) { + getTopicData(name).m_topic.deleteProperty(propName); + } + + /** + * Gets all topic properties as a JSON object string. Each key in the object is the property name, + * and the corresponding value is the property value. + * + * @param name name + * @return JSON string + */ + @Override + public String getProperties(String name) { + return getTopicData(name).m_topic.getProperties(); + } + + /** + * Updates multiple topic properties. Each key in the passed-in object is the name of the property + * to add/update, and the corresponding value is the property value to set for that property. Null + * values result in deletion of the corresponding property. + * + * @param name name + * @param properties map of keys/JSON values to add/update/delete + * @throws IllegalArgumentException if properties is not a JSON object + */ + @Override + public void setProperties(String name, Map properties) { + if (properties.isEmpty()) { + return; + } + // convert properties to a single JSON string + StringBuilder jsonProperties = new StringBuilder(); + jsonProperties.append('{'); + properties.forEach( + (k, v) -> { + jsonProperties.append('"'); + jsonProperties.append(k.replace("\"", "\\\"")); + jsonProperties.append("\":"); + jsonProperties.append(v); + jsonProperties.append(','); + }); + // replace the trailing comma with a } + jsonProperties.setCharAt(jsonProperties.length() - 1, '}'); + getTopicData(name).m_topic.setProperties(jsonProperties.toString()); + } + + @Override + public void remove(String name) { + TopicData data = m_topics.remove(name); + if (data != null) { + data.close(); + } + } + + @Override + public void addPeriodic(Runnable runnable) { + // TODO + } + + /** + * Return whether this sendable has been published. + * + * @return True if it has been published, false if not. + */ + @Override + public boolean isPublished() { + return true; + } + + /** Update the published values by calling the getters for all properties. */ + @Override + public void update() { + if (isClosed()) { + return; + } + for (TopicData data : m_topics.values()) { + GenericPublisher publisher = data.getPublisher(); + Consumer consumer = data.getPolledUpdate(); + if (publisher != null && consumer != null) { + consumer.accept(publisher); + } + } + for (NetworkSendableTable table : m_tables.values()) { + if (!table.isClosed()) { + table.update(); + } + } + } + + /** Erases all publishers and subscribers. */ + @Override + public void clear() { + for (TopicData data : m_topics.values()) { + data.close(); + } + m_topics.clear(); + for (NetworkSendableTable table : m_tables.values()) { + table.close(); + } + m_tables.clear(); + } + + /** + * Returns whether there is a data schema already registered with the given name that this + * instance has published. This does NOT perform a check as to whether the schema has already been + * published by another node on the network. + * + * @param typeString Name (the string passed as the data type for topics using this schema) + * @return True if schema already registered + */ + @Override + public boolean hasSchema(String typeString) { + return m_inst.hasSchema(typeString); + } + + /** + * Registers a data schema. Data schemas provide information for how a certain data type string + * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. + * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas + * are published just like normal topics, with the name being generated from the provided name: + * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. + * + * @param typeString Name (the string passed as the data type for topics using this schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + @Override + public void addSchema(String typeString, String type, byte[] schema) { + m_inst.addSchema(typeString, type, schema); + } + + /** + * Registers a data schema. Data schemas provide information for how a certain data type string + * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. + * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas + * are published just like normal topics, with the name being generated from the provided name: + * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. + * + * @param typeString Name (the string passed as the data type for topics using this schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + @Override + public void addSchema(String typeString, String type, String schema) { + m_inst.addSchema(typeString, type, schema); + } + + /** + * Registers a protobuf schema. Duplicate calls to this function with the same name are silently + * ignored. + * + * @param proto protobuf serialization object + */ + @Override + public void addSchema(Protobuf proto) { + m_inst.addSchema(proto); + } + + /** + * Registers a struct schema. Duplicate calls to this function with the same name are silently + * ignored. + * + * @param struct struct serialization object + */ + @Override + public void addSchema(Struct struct) { + m_inst.addSchema(struct); + } + + /** + * Return whether close() has been called on this sendable table. + * + * @return True if closed, false otherwise. + */ + @Override + public boolean isClosed() { + return m_closed; + } + + @Override + public void close() { + clear(); + if (m_closeSendable != null) { + m_closeSendable.accept(this); + m_closeSendable = null; + } + m_closed = true; + } +} diff --git a/styleguide/spotbugs-exclude.xml b/styleguide/spotbugs-exclude.xml index d655efb9ac1..e6ffa96a728 100644 --- a/styleguide/spotbugs-exclude.xml +++ b/styleguide/spotbugs-exclude.xml @@ -30,6 +30,10 @@ + + + + diff --git a/wpilibc/src/main/native/cpp/AnalogInput.cpp b/wpilibc/src/main/native/cpp/AnalogInput.cpp index 2ecb2df6b93..fc4beeb84e0 100644 --- a/wpilibc/src/main/native/cpp/AnalogInput.cpp +++ b/wpilibc/src/main/native/cpp/AnalogInput.cpp @@ -10,8 +10,7 @@ #include #include #include -#include -#include +#include #include "frc/Errors.h" #include "frc/SensorUtil.h" @@ -33,8 +32,6 @@ AnalogInput::AnalogInput(int channel) { FRC_CheckErrorStatus(status, "Channel {}", channel); HAL_Report(HALUsageReporting::kResourceType_AnalogChannel, channel + 1); - - wpi::SendableRegistry::AddLW(this, "AnalogInput", channel); } AnalogInput::~AnalogInput() { @@ -194,8 +191,7 @@ void AnalogInput::SetSimDevice(HAL_SimDeviceHandle device) { HAL_SetAnalogInputSimDevice(m_port, device); } -void AnalogInput::InitSendable(wpi::SendableBuilder& builder) { - builder.SetSmartDashboardType("Analog Input"); - builder.AddDoubleProperty( - "Value", [=, this] { return GetAverageVoltage(); }, nullptr); +void wpi2::Sendable::Init(frc::AnalogInput* obj, + SendableTable& table) { + table.PublishDouble("Value", [obj] { return obj->GetAverageVoltage(); }); } diff --git a/wpilibc/src/main/native/cpp/AnalogTrigger.cpp b/wpilibc/src/main/native/cpp/AnalogTrigger.cpp index ea33c73f8dd..93088c8e7f2 100644 --- a/wpilibc/src/main/native/cpp/AnalogTrigger.cpp +++ b/wpilibc/src/main/native/cpp/AnalogTrigger.cpp @@ -9,7 +9,7 @@ #include #include #include -#include +#include #include "frc/AnalogInput.h" #include "frc/DutyCycle.h" @@ -20,7 +20,6 @@ using namespace frc; AnalogTrigger::AnalogTrigger(int channel) : AnalogTrigger(new AnalogInput(channel)) { m_ownsAnalog = true; - wpi::SendableRegistry::AddChild(this, m_analogInput); } AnalogTrigger::AnalogTrigger(AnalogInput* input) { @@ -31,7 +30,6 @@ AnalogTrigger::AnalogTrigger(AnalogInput* input) { int index = GetIndex(); HAL_Report(HALUsageReporting::kResourceType_AnalogTrigger, index + 1); - wpi::SendableRegistry::AddLW(this, "AnalogTrigger", index); } AnalogTrigger::AnalogTrigger(DutyCycle* input) { @@ -42,7 +40,6 @@ AnalogTrigger::AnalogTrigger(DutyCycle* input) { int index = GetIndex(); HAL_Report(HALUsageReporting::kResourceType_AnalogTrigger, index + 1); - wpi::SendableRegistry::AddLW(this, "AnalogTrigger", index); } AnalogTrigger::~AnalogTrigger() { @@ -112,12 +109,6 @@ std::shared_ptr AnalogTrigger::CreateOutput( new AnalogTriggerOutput(*this, type)); } -void AnalogTrigger::InitSendable(wpi::SendableBuilder& builder) { - if (m_ownsAnalog) { - m_analogInput->InitSendable(builder); - } -} - int AnalogTrigger::GetSourceChannel() const { if (m_analogInput) { return m_analogInput->GetChannel(); @@ -127,3 +118,10 @@ int AnalogTrigger::GetSourceChannel() const { return -1; } } + +void wpi2::Sendable::Init(frc::AnalogTrigger* obj, + SendableTable& table) { + if (obj->m_ownsAnalog) { + Sendable::Init(obj->m_analogInput, table); + } +} diff --git a/wpilibc/src/main/native/cpp/telemetry/Telemetry.cpp b/wpilibc/src/main/native/cpp/telemetry/Telemetry.cpp new file mode 100644 index 00000000000..1b6040beb6c --- /dev/null +++ b/wpilibc/src/main/native/cpp/telemetry/Telemetry.cpp @@ -0,0 +1,191 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "frc/telemetry/Telemetry.h" + +#include +#include + +using namespace frc; + +wpi2::SendableTable& Telemetry::GetTableHolder() { + static wpi2::SendableTable instance{nullptr}; // FIXME + return instance; +} + +void Telemetry::SetTable(wpi2::SendableTable table) { + GetTableHolder() = std::move(table); +} + +wpi2::SendableTable Telemetry::GetTable() { + return GetTableHolder(); +} + +void Telemetry::SetBoolean(std::string_view name, bool value) { + GetTableHolder().SetBoolean(name, value); +} + +void Telemetry::SetInteger(std::string_view name, int64_t value) { + GetTableHolder().SetInteger(name, value); +} + +void Telemetry::SetFloat(std::string_view name, float value) { + GetTableHolder().SetFloat(name, value); +} + +void Telemetry::SetDouble(std::string_view name, double value) { + GetTableHolder().SetDouble(name, value); +} + +void Telemetry::SetString(std::string_view name, std::string_view value) { + GetTableHolder().SetString(name, value); +} + +void Telemetry::SetRaw(std::string_view name, std::string_view typeString, + std::span value) { + GetTableHolder().SetRaw(name, typeString, value); +} + +void Telemetry::PublishBoolean(std::string_view name, + std::function supplier) { + GetTableHolder().PublishBoolean(name, std::move(supplier)); +} + +void Telemetry::PublishInteger(std::string_view name, + std::function supplier) { + GetTableHolder().PublishInteger(name, std::move(supplier)); +} + +void Telemetry::PublishFloat(std::string_view name, + std::function supplier) { + GetTableHolder().PublishFloat(name, std::move(supplier)); +} + +void Telemetry::PublishDouble(std::string_view name, + std::function supplier) { + GetTableHolder().PublishDouble(name, std::move(supplier)); +} + +void Telemetry::PublishString(std::string_view name, + std::function supplier) { + GetTableHolder().PublishString(name, std::move(supplier)); +} + +void Telemetry::PublishRaw(std::string_view name, std::string_view typeString, + std::function()> supplier) { + GetTableHolder().PublishRaw(name, typeString, std::move(supplier)); +} + +void Telemetry::PublishRawSmall( + std::string_view name, std::string_view typeString, + std::function(wpi::SmallVectorImpl& buf)> + supplier) { + GetTableHolder().PublishRawSmall(name, typeString, std::move(supplier)); +} + +std::function Telemetry::AddBooleanPublisher( + std::string_view name) { + return GetTableHolder().AddBooleanPublisher(name); +} + +std::function Telemetry::AddIntegerPublisher( + std::string_view name) { + return GetTableHolder().AddIntegerPublisher(name); +} + +std::function Telemetry::AddFloatPublisher(std::string_view name) { + return GetTableHolder().AddFloatPublisher(name); +} + +std::function Telemetry::AddDoublePublisher( + std::string_view name) { + return GetTableHolder().AddDoublePublisher(name); +} + +std::function Telemetry::AddStringPublisher( + std::string_view name) { + return GetTableHolder().AddStringPublisher(name); +} + +std::function)> Telemetry::AddRawPublisher( + std::string_view name, std::string_view typeString) { + return GetTableHolder().AddRawPublisher(name, typeString); +} + +void Telemetry::SubscribeBoolean(std::string_view name, + std::function consumer) { + GetTableHolder().SubscribeBoolean(name, std::move(consumer)); +} + +void Telemetry::SubscribeInteger(std::string_view name, + std::function consumer) { + GetTableHolder().SubscribeInteger(name, std::move(consumer)); +} + +void Telemetry::SubscribeFloat(std::string_view name, + std::function consumer) { + GetTableHolder().SubscribeFloat(name, std::move(consumer)); +} + +void Telemetry::SubscribeDouble(std::string_view name, + std::function consumer) { + GetTableHolder().SubscribeDouble(name, std::move(consumer)); +} + +void Telemetry::SubscribeString( + std::string_view name, std::function consumer) { + GetTableHolder().SubscribeString(name, std::move(consumer)); +} + +void Telemetry::SubscribeRaw( + std::string_view name, std::string_view typeString, + std::function)> consumer) { + GetTableHolder().SubscribeRaw(name, typeString, std::move(consumer)); +} + +wpi2::SendableTable Telemetry::GetChild(std::string_view name) { + return GetTableHolder().GetChild(name); +} + +void Telemetry::SetPublishOptions(std::string_view name, + const wpi2::SendableOptions& options) { + GetTableHolder().SetPublishOptions(name, options); +} + +void Telemetry::SetSubscribeOptions(std::string_view name, + const wpi2::SendableOptions& options) { + GetTableHolder().SetSubscribeOptions(name, options); +} + +wpi::json Telemetry::GetProperty(std::string_view name, + std::string_view propName) { + return GetTableHolder().GetProperty(name, propName); +} + +void Telemetry::SetProperty(std::string_view name, std::string_view propName, + const wpi::json& value) { + GetTableHolder().SetProperty(name, propName, value); +} + +void Telemetry::DeleteProperty(std::string_view name, + std::string_view propName) { + GetTableHolder().DeleteProperty(name, propName); +} + +wpi::json Telemetry::GetProperties(std::string_view name) { + return GetTableHolder().GetProperties(name); +} + +bool Telemetry::SetProperties(std::string_view name, + const wpi::json& properties) { + return GetTableHolder().SetProperties(name, properties); +} + +void Telemetry::Remove(std::string_view name) { + GetTableHolder().Remove(name); +} + +void Telemetry::Clear() { + GetTableHolder().Clear(); +} diff --git a/wpilibc/src/main/native/include/frc/AnalogInput.h b/wpilibc/src/main/native/include/frc/AnalogInput.h index ebc4e701503..cb4a4b41b41 100644 --- a/wpilibc/src/main/native/include/frc/AnalogInput.h +++ b/wpilibc/src/main/native/include/frc/AnalogInput.h @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include namespace frc { @@ -27,8 +27,7 @@ class DMASample; * are divided by the number of samples to retain the resolution, but get more * stable values. */ -class AnalogInput : public wpi::Sendable, - public wpi::SendableHelper { +class AnalogInput : public wpi::MoveTrackerBase { friend class AnalogTrigger; friend class AnalogGyro; friend class DMA; @@ -280,8 +279,6 @@ class AnalogInput : public wpi::Sendable, */ void SetSimDevice(HAL_SimDeviceHandle device); - void InitSendable(wpi::SendableBuilder& builder) override; - private: int m_channel; hal::Handle m_port; @@ -289,3 +286,16 @@ class AnalogInput : public wpi::Sendable, }; } // namespace frc + +namespace wpi2 { + +template<> +struct Sendable { + static constexpr std::string_view GetTypeString() { return "Analog Input"; } + static void Init(frc::AnalogInput* obj, SendableTable& table); + static void Close(frc::AnalogInput* obj, SendableTable& table) {} +}; + +static_assert(SendableSerializable); + +} // namespace wpi2 diff --git a/wpilibc/src/main/native/include/frc/AnalogTrigger.h b/wpilibc/src/main/native/include/frc/AnalogTrigger.h index f9c56026667..df2e2554172 100644 --- a/wpilibc/src/main/native/include/frc/AnalogTrigger.h +++ b/wpilibc/src/main/native/include/frc/AnalogTrigger.h @@ -7,8 +7,8 @@ #include #include -#include -#include +#include +#include #include "frc/AnalogTriggerOutput.h" @@ -17,9 +17,9 @@ namespace frc { class AnalogInput; class DutyCycle; -class AnalogTrigger : public wpi::Sendable, - public wpi::SendableHelper { +class AnalogTrigger : public wpi::MoveTrackerBase { friend class AnalogTriggerOutput; + friend struct wpi2::Sendable; public: /** @@ -148,8 +148,6 @@ class AnalogTrigger : public wpi::Sendable, std::shared_ptr CreateOutput( AnalogTriggerType type) const; - void InitSendable(wpi::SendableBuilder& builder) override; - private: int GetSourceChannel() const; @@ -160,3 +158,16 @@ class AnalogTrigger : public wpi::Sendable, }; } // namespace frc + +namespace wpi2 { + +template <> +struct Sendable { + static constexpr std::string_view GetTypeString() { return "Analog Input"; } + static void Init(frc::AnalogTrigger* obj, SendableTable& table); + static void Close(frc::AnalogTrigger* obj, SendableTable& table) {} +}; + +static_assert(SendableSerializable); + +} // namespace wpi2 diff --git a/wpilibc/src/main/native/include/frc/telemetry/Telemetry.h b/wpilibc/src/main/native/include/frc/telemetry/Telemetry.h new file mode 100644 index 00000000000..1781d6c39ad --- /dev/null +++ b/wpilibc/src/main/native/include/frc/telemetry/Telemetry.h @@ -0,0 +1,226 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include +#include + +namespace wpi2 { +class SendableTable; +} // namespace wpi2 + +namespace frc { + +class Telemetry { + public: + Telemetry() = delete; + + static void SetTable(wpi2::SendableTable table); + + static wpi2::SendableTable GetTable(); + + static void SetBoolean(std::string_view name, bool value); + + static void SetInteger(std::string_view name, int64_t value); + + static void SetFloat(std::string_view name, float value); + + static void SetDouble(std::string_view name, double value); + + static void SetString(std::string_view name, std::string_view value); + + static void SetRaw(std::string_view name, std::string_view typeString, + std::span value); + + template + requires wpi::StructSerializable + static void SetStruct(std::string_view name, const T& value, I... info); + + template + static void SetProtobuf(std::string_view name, const T& value); + + static void PublishBoolean(std::string_view name, + std::function supplier); + + static void PublishInteger(std::string_view name, + std::function supplier); + + static void PublishFloat(std::string_view name, + std::function supplier); + + static void PublishDouble(std::string_view name, + std::function supplier); + + static void PublishString(std::string_view name, + std::function supplier); + + static void PublishRaw(std::string_view name, std::string_view typeString, + std::function()> supplier); + + static void PublishRawSmall( + std::string_view name, std::string_view typeString, + std::function(wpi::SmallVectorImpl& buf)> + supplier); + + template + requires wpi::StructSerializable + static void PublishStruct(std::string_view name, std::function supplier, + I... info); + + template + static void PublishProtobuf(std::string_view name, + std::function supplier); + + [[nodiscard]] + static std::function AddBooleanPublisher(std::string_view name); + + [[nodiscard]] + static std::function AddIntegerPublisher( + std::string_view name); + + [[nodiscard]] + static std::function AddFloatPublisher(std::string_view name); + + [[nodiscard]] + static std::function AddDoublePublisher(std::string_view name); + + [[nodiscard]] + static std::function AddStringPublisher( + std::string_view name); + + [[nodiscard]] + static std::function)> AddRawPublisher( + std::string_view name, std::string_view typeString); + + template + requires wpi::StructSerializable + [[nodiscard]] + static std::function AddStructPublisher(std::string_view name, + I... info); + + template + [[nodiscard]] + static std::function AddProtobufPublisher( + std::string_view name); + + static void SubscribeBoolean(std::string_view name, + std::function consumer); + + static void SubscribeInteger(std::string_view name, + std::function consumer); + + static void SubscribeFloat(std::string_view name, + std::function consumer); + + static void SubscribeDouble(std::string_view name, + std::function consumer); + + static void SubscribeString(std::string_view name, + std::function consumer); + + static void SubscribeRaw( + std::string_view name, std::string_view typeString, + std::function)> consumer); + + template + requires wpi::StructSerializable + static void SubscribeStruct(std::string_view name, + std::function consumer, I... info); + + template + static void SubscribeProtobuf(std::string_view name, + std::function consumer); + + template + requires wpi2::SendableSerializableMoveTracked + static wpi2::SendableTable AddSendable(std::string_view name, T* obj, + I... info); + + template + requires wpi2::SendableSerializableSharedPointer + static wpi2::SendableTable AddSendable(std::string_view name, + std::shared_ptr obj, I... info); + + static wpi2::SendableTable GetChild(std::string_view name); + + static void SetPublishOptions(std::string_view name, + const wpi2::SendableOptions& options); + + static void SetSubscribeOptions(std::string_view name, + const wpi2::SendableOptions& options); + + /** + * Gets the current value of a property (as a JSON object). + * + * @param name name + * @param propName property name + * @return JSON object; null object if the property does not exist. + */ + static wpi::json GetProperty(std::string_view name, + std::string_view propName); + + /** + * Sets a property value. + * + * @param name name + * @param propName property name + * @param value property value + */ + static void SetProperty(std::string_view name, std::string_view propName, + const wpi::json& value); + + /** + * Deletes a property. Has no effect if the property does not exist. + * + * @param name name + * @param propName property name + */ + static void DeleteProperty(std::string_view name, std::string_view propName); + + /** + * Gets all topic properties as a JSON object. Each key in the object + * is the property name, and the corresponding value is the property value. + * + * @param name name + * @return JSON object + */ + static wpi::json GetProperties(std::string_view name); + + /** + * Updates multiple topic properties. Each key in the passed-in object is + * the name of the property to add/update, and the corresponding value is the + * property value to set for that property. Null values result in deletion + * of the corresponding property. + * + * @param name name + * @param properties JSON object with keys to add/update/delete + * @return False if properties is not an object + */ + static bool SetProperties(std::string_view name, const wpi::json& properties); + + static void Remove(std::string_view name); + + /** + * Erases all publishers and subscribers. + */ + static void Clear(); + + private: + static wpi2::SendableTable& GetTableHolder(); +}; + +} // namespace frc + +#include "frc/telemetry/Telemetry.inc" diff --git a/wpilibc/src/main/native/include/frc/telemetry/Telemetry.inc b/wpilibc/src/main/native/include/frc/telemetry/Telemetry.inc new file mode 100644 index 00000000000..84f377abdc5 --- /dev/null +++ b/wpilibc/src/main/native/include/frc/telemetry/Telemetry.inc @@ -0,0 +1,87 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include "frc/telemetry/Telemetry.h" + +#include +#include +#include + +#include + +namespace frc { + +template + requires wpi::StructSerializable +inline void Telemetry::SetStruct(std::string_view name, const T& value, + I... info) { + GetTableHolder().SetStruct(name, value, std::forward(info)...); +} + +template +inline void Telemetry::SetProtobuf(std::string_view name, const T& value) { + GetTableHolder().SetProtobuf(name, value); +} + +template + requires wpi::StructSerializable +inline void Telemetry::PublishStruct(std::string_view name, + std::function supplier, I... info) { + GetTableHolder().PublishStruct(name, std::move(supplier), + std::forward(info)...); +} + +template +inline void Telemetry::PublishProtobuf(std::string_view name, + std::function supplier) { + GetTableHolder().PublishProtobuf(name, std::move(supplier)); +} + +template + requires wpi::StructSerializable +inline std::function Telemetry::AddStructPublisher( + std::string_view name, I... info) { + return GetTableHolder().AddStructPublisher(name, std::forward(info)...); +} + +template +inline std::function Telemetry::AddProtobufPublisher( + std::string_view name) { + return GetTableHolder().AddProtobufPublisher(name); +} + +template + requires wpi::StructSerializable +inline void Telemetry::SubscribeStruct(std::string_view name, + std::function consumer, + I... info) { + GetTableHolder().SubscribeStruct(name, std::move(consumer), + std::forward(info)...); +} + +template +inline void Telemetry::SubscribeProtobuf(std::string_view name, + std::function consumer) { + GetTableHolder().SubscribeProtobuf(name, std::move(consumer)); +} + +template + requires wpi2::SendableSerializableMoveTracked +inline wpi2::SendableTable Telemetry::AddSendable(std::string_view name, T* obj, + I... info) { + return GetTableHolder().AddSendable(name, obj, std::forward(info)...); +} + +template + requires wpi2::SendableSerializableSharedPointer +inline wpi2::SendableTable Telemetry::AddSendable(std::string_view name, + std::shared_ptr obj, + I... info) { + return GetTableHolder().AddSendable(name, std::move(obj), + std::forward(info)...); +} + +} // namespace frc diff --git a/wpilibj/CMakeLists.txt b/wpilibj/CMakeLists.txt index 440f7585592..ff6309b2038 100644 --- a/wpilibj/CMakeLists.txt +++ b/wpilibj/CMakeLists.txt @@ -25,6 +25,7 @@ if(WITH_JAVA) file(GLOB_RECURSE JAVA_SOURCES src/main/java/*.java src/generated/main/java/*.java) file(GLOB EJML_JARS "${WPILIB_BINARY_DIR}/wpimath/thirdparty/ejml/*.jar") file(GLOB JACKSON_JARS "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/jackson/*.jar") + file(GLOB QUICKBUF_JAR "${WPILIB_BINARY_DIR}/wpiutil/thirdparty/quickbuf/*.jar") add_jar( wpilibj_jar @@ -35,6 +36,7 @@ if(WITH_JAVA) ntcore_jar ${EJML_JARS} ${JACKSON_JARS} + ${QUICKBUF_JAR} ${OPENCV_JAR_FILE} cscore_jar cameraserver_jar diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_I2C.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_I2C.java index df5ee80db6f..36ae5c9bf58 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_I2C.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_I2C.java @@ -10,11 +10,12 @@ import edu.wpi.first.hal.SimDevice; import edu.wpi.first.hal.SimDouble; import edu.wpi.first.hal.SimEnum; -import edu.wpi.first.networktables.DoublePublisher; -import edu.wpi.first.networktables.DoubleTopic; -import edu.wpi.first.networktables.NTSendable; -import edu.wpi.first.networktables.NTSendableBuilder; -import edu.wpi.first.util.sendable.SendableRegistry; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableSerializable; +import edu.wpi.first.util.sendable2.SendableTable; +import edu.wpi.first.util.struct.Struct; +import edu.wpi.first.util.struct.StructSerializable; + import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -26,7 +27,7 @@ * WPILib Known Issues page for details. */ @SuppressWarnings("TypeName") -public class ADXL345_I2C implements NTSendable, AutoCloseable { +public class ADXL345_I2C implements SendableSerializable, AutoCloseable { /** Default I2C device address. */ public static final byte kAddress = 0x1D; @@ -77,7 +78,7 @@ public enum Axes { /** Container type for accelerations from all axes. */ @SuppressWarnings("MemberName") - public static class AllAxes { + public static class AllAxes implements StructSerializable { /** Acceleration along the X axis in g-forces. */ public double XAxis; @@ -89,6 +90,46 @@ public static class AllAxes { /** Default constructor. */ public AllAxes() {} + + public static class AllAxesStruct implements Struct { + @Override + public Class getTypeClass() { + return AllAxes.class; + } + + @Override + public String getTypeName() { + return "ADXL345_I2C.AllAxes"; + } + + @Override + public int getSize() { + return 24; + } + + @Override + public String getSchema() { + return "double XAxis;double YAxis;double ZAxis"; + } + + @Override + public AllAxes unpack(ByteBuffer bb) { + AllAxes val = new AllAxes(); + val.XAxis = bb.getDouble(); + val.YAxis = bb.getDouble(); + val.ZAxis = bb.getDouble(); + return val; + } + + @Override + public void pack(ByteBuffer bb, AllAxes value) { + bb.putDouble(value.XAxis); + bb.putDouble(value.YAxis); + bb.putDouble(value.ZAxis); + } + } + + public static final AllAxesStruct struct = new AllAxesStruct(); } private I2C m_i2c; @@ -141,7 +182,6 @@ public ADXL345_I2C(I2C.Port port, Range range, int deviceAddress) { setRange(range); HAL.report(tResourceType.kResourceType_ADXL345, tInstances.kADXL345_I2C); - SendableRegistry.addLW(this, "ADXL345_I2C", port.value); } /** @@ -164,7 +204,6 @@ public int getDeviceAddress() { @Override public void close() { - SendableRegistry.remove(this); if (m_i2c != null) { m_i2c.close(); m_i2c = null; @@ -273,21 +312,28 @@ public AllAxes getAccelerations() { return data; } - @Override - public void initSendable(NTSendableBuilder builder) { - builder.setSmartDashboardType("3AxisAccelerometer"); - DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish(); - DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish(); - DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish(); - builder.addCloseable(pubX); - builder.addCloseable(pubY); - builder.addCloseable(pubZ); - builder.setUpdateTable( - () -> { - AllAxes data = getAccelerations(); - pubX.set(data.XAxis); - pubY.set(data.YAxis); - pubZ.set(data.ZAxis); - }); + public static class ADXL345I2CSendable implements Sendable { + @Override + public Class getTypeClass() { + return ADXL345_I2C.class; + } + + @Override + public String getTypeString() { + return "3AxisAccelerometer"; + } + + @Override + public boolean isClosed(ADXL345_I2C obj) { + return obj.m_i2c == null; + } + + @Override + public void initSendable(ADXL345_I2C obj, SendableTable table) { + table.publishStruct("Value", AllAxes.struct, obj::getAccelerations); + } } + + /** Sendable for serialization. */ + public static final ADXL345I2CSendable sendable = new ADXL345I2CSendable(); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_SPI.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_SPI.java index a64d904d21c..18eecdd20f6 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_SPI.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL345_SPI.java @@ -10,17 +10,16 @@ import edu.wpi.first.hal.SimDevice; import edu.wpi.first.hal.SimDouble; import edu.wpi.first.hal.SimEnum; -import edu.wpi.first.networktables.DoublePublisher; -import edu.wpi.first.networktables.DoubleTopic; -import edu.wpi.first.networktables.NTSendable; -import edu.wpi.first.networktables.NTSendableBuilder; -import edu.wpi.first.util.sendable.SendableRegistry; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableSerializable; +import edu.wpi.first.util.sendable2.SendableTable; +import edu.wpi.first.util.struct.Struct; import java.nio.ByteBuffer; import java.nio.ByteOrder; /** ADXL345 SPI Accelerometer. */ @SuppressWarnings("TypeName") -public class ADXL345_SPI implements NTSendable, AutoCloseable { +public class ADXL345_SPI implements SendableSerializable, AutoCloseable { private static final int kPowerCtlRegister = 0x2D; private static final int kDataFormatRegister = 0x31; private static final int kDataRegister = 0x32; @@ -84,6 +83,46 @@ public static class AllAxes { /** Default constructor. */ public AllAxes() {} + + public static class AllAxesStruct implements Struct { + @Override + public Class getTypeClass() { + return AllAxes.class; + } + + @Override + public String getTypeName() { + return "ADXL345_SPI.AllAxes"; + } + + @Override + public int getSize() { + return 24; + } + + @Override + public String getSchema() { + return "double XAxis;double YAxis;double ZAxis"; + } + + @Override + public AllAxes unpack(ByteBuffer bb) { + AllAxes val = new AllAxes(); + val.XAxis = bb.getDouble(); + val.YAxis = bb.getDouble(); + val.ZAxis = bb.getDouble(); + return val; + } + + @Override + public void pack(ByteBuffer bb, AllAxes value) { + bb.putDouble(value.XAxis); + bb.putDouble(value.YAxis); + bb.putDouble(value.ZAxis); + } + } + + public static final AllAxesStruct struct = new AllAxesStruct(); } private SPI m_spi; @@ -118,7 +157,6 @@ public ADXL345_SPI(SPI.Port port, Range range) { m_simZ = m_simDevice.createDouble("z", SimDevice.Direction.kInput, 0.0); } init(range); - SendableRegistry.addLW(this, "ADXL345_SPI", port.value); } /** @@ -132,7 +170,6 @@ public int getPort() { @Override public void close() { - SendableRegistry.remove(this); if (m_spi != null) { m_spi.close(); m_spi = null; @@ -269,21 +306,28 @@ public ADXL345_SPI.AllAxes getAccelerations() { return data; } - @Override - public void initSendable(NTSendableBuilder builder) { - builder.setSmartDashboardType("3AxisAccelerometer"); - DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish(); - DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish(); - DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish(); - builder.addCloseable(pubX); - builder.addCloseable(pubY); - builder.addCloseable(pubZ); - builder.setUpdateTable( - () -> { - AllAxes data = getAccelerations(); - pubX.set(data.XAxis); - pubY.set(data.YAxis); - pubZ.set(data.ZAxis); - }); + public static class ADXL345SPISendable implements Sendable { + @Override + public Class getTypeClass() { + return ADXL345_SPI.class; + } + + @Override + public String getTypeString() { + return "3AxisAccelerometer"; + } + + @Override + public boolean isClosed(ADXL345_SPI obj) { + return obj.m_spi == null; + } + + @Override + public void initSendable(ADXL345_SPI obj, SendableTable table) { + table.publishStruct("Value", AllAxes.struct, obj::getAccelerations); + } } + + /** Sendable for serialization. */ + public static final ADXL345SPISendable sendable = new ADXL345SPISendable(); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL362.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL362.java index f33dc7c7a73..8e48db9f3ab 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL362.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXL362.java @@ -9,11 +9,10 @@ import edu.wpi.first.hal.SimDevice; import edu.wpi.first.hal.SimDouble; import edu.wpi.first.hal.SimEnum; -import edu.wpi.first.networktables.DoublePublisher; -import edu.wpi.first.networktables.DoubleTopic; -import edu.wpi.first.networktables.NTSendable; -import edu.wpi.first.networktables.NTSendableBuilder; -import edu.wpi.first.util.sendable.SendableRegistry; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableSerializable; +import edu.wpi.first.util.sendable2.SendableTable; +import edu.wpi.first.util.struct.Struct; import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -22,7 +21,7 @@ * *

This class allows access to an Analog Devices ADXL362 3-axis accelerometer. */ -public class ADXL362 implements NTSendable, AutoCloseable { +public class ADXL362 implements SendableSerializable, AutoCloseable { private static final byte kRegWrite = 0x0A; private static final byte kRegRead = 0x0B; @@ -84,6 +83,46 @@ public static class AllAxes { /** Default constructor. */ public AllAxes() {} + + public static class AllAxesStruct implements Struct { + @Override + public Class getTypeClass() { + return AllAxes.class; + } + + @Override + public String getTypeName() { + return "ADXL362.AllAxes"; + } + + @Override + public int getSize() { + return 24; + } + + @Override + public String getSchema() { + return "double XAxis;double YAxis;double ZAxis"; + } + + @Override + public AllAxes unpack(ByteBuffer bb) { + AllAxes val = new AllAxes(); + val.XAxis = bb.getDouble(); + val.YAxis = bb.getDouble(); + val.ZAxis = bb.getDouble(); + return val; + } + + @Override + public void pack(ByteBuffer bb, AllAxes value) { + bb.putDouble(value.XAxis); + bb.putDouble(value.YAxis); + bb.putDouble(value.ZAxis); + } + } + + public static final AllAxesStruct struct = new AllAxesStruct(); } private SPI m_spi; @@ -157,7 +196,6 @@ public ADXL362(SPI.Port port, Range range) { m_spi.write(transferBuffer, 3); HAL.report(tResourceType.kResourceType_ADXL362, port.value + 1); - SendableRegistry.addLW(this, "ADXL362", port.value); } /** @@ -171,7 +209,6 @@ public int getPort() { @Override public void close() { - SendableRegistry.remove(this); if (m_spi != null) { m_spi.close(); m_spi = null; @@ -304,21 +341,28 @@ public ADXL362.AllAxes getAccelerations() { return data; } - @Override - public void initSendable(NTSendableBuilder builder) { - builder.setSmartDashboardType("3AxisAccelerometer"); - DoublePublisher pubX = new DoubleTopic(builder.getTopic("X")).publish(); - DoublePublisher pubY = new DoubleTopic(builder.getTopic("Y")).publish(); - DoublePublisher pubZ = new DoubleTopic(builder.getTopic("Z")).publish(); - builder.addCloseable(pubX); - builder.addCloseable(pubY); - builder.addCloseable(pubZ); - builder.setUpdateTable( - () -> { - AllAxes data = getAccelerations(); - pubX.set(data.XAxis); - pubY.set(data.YAxis); - pubZ.set(data.ZAxis); - }); + public static class ADXL362Sendable implements Sendable { + @Override + public Class getTypeClass() { + return ADXL362.class; + } + + @Override + public String getTypeString() { + return "3AxisAccelerometer"; + } + + @Override + public boolean isClosed(ADXL362 obj) { + return obj.m_spi == null; + } + + @Override + public void initSendable(ADXL362 obj, SendableTable table) { + table.publishStruct("Value", AllAxes.struct, obj::getAccelerations); + } } + + /** Sendable for serialization. */ + public static final ADXL362Sendable sendable = new ADXL362Sendable(); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXRS450_Gyro.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXRS450_Gyro.java index d574a24824a..2e4fcd2f5d4 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXRS450_Gyro.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/ADXRS450_Gyro.java @@ -10,9 +10,10 @@ import edu.wpi.first.hal.SimDevice; import edu.wpi.first.hal.SimDouble; import edu.wpi.first.math.geometry.Rotation2d; -import edu.wpi.first.util.sendable.Sendable; -import edu.wpi.first.util.sendable.SendableBuilder; -import edu.wpi.first.util.sendable.SendableRegistry; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableSerializable; +import edu.wpi.first.util.sendable2.SendableTable; + import java.nio.ByteBuffer; import java.nio.ByteOrder; @@ -27,7 +28,7 @@ * an ADXRS Gyro is supported. */ @SuppressWarnings("TypeName") -public class ADXRS450_Gyro implements Sendable, AutoCloseable { +public class ADXRS450_Gyro implements SendableSerializable, AutoCloseable { private static final double kSamplePeriod = 0.0005; private static final double kCalibrationSampleTime = 5.0; private static final double kDegreePerSecondPerLSB = 0.0125; @@ -91,7 +92,6 @@ public ADXRS450_Gyro(SPI.Port port) { } HAL.report(tResourceType.kResourceType_ADXRS450, port.value + 1); - SendableRegistry.addLW(this, "ADXRS450_Gyro", port.value); } /** @@ -186,7 +186,6 @@ public void reset() { /** Delete (free) the spi port used for the gyro and stop accumulating. */ @Override public void close() { - SendableRegistry.remove(this); if (m_spi != null) { m_spi.close(); m_spi = null; @@ -259,9 +258,28 @@ public Rotation2d getRotation2d() { return Rotation2d.fromDegrees(-getAngle()); } - @Override - public void initSendable(SendableBuilder builder) { - builder.setSmartDashboardType("Gyro"); - builder.addDoubleProperty("Value", this::getAngle, null); + public static class ADXRS450GyroSendable implements Sendable { + @Override + public Class getTypeClass() { + return ADXRS450_Gyro.class; + } + + @Override + public String getTypeString() { + return "Gyro"; + } + + @Override + public boolean isClosed(ADXRS450_Gyro obj) { + return !obj.isConnected(); + } + + @Override + public void initSendable(ADXRS450_Gyro obj, SendableTable table) { + table.publishDouble("Value", obj::getAngle); + } } + + /** Sendable for serialization. */ + public static final ADXRS450GyroSendable sendable = new ADXRS450GyroSendable(); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogInput.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogInput.java index 3c6458bfad4..5040d8713a0 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogInput.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogInput.java @@ -10,9 +10,9 @@ import edu.wpi.first.hal.HAL; import edu.wpi.first.hal.SimDevice; import edu.wpi.first.hal.util.AllocationException; -import edu.wpi.first.util.sendable.Sendable; -import edu.wpi.first.util.sendable.SendableBuilder; -import edu.wpi.first.util.sendable.SendableRegistry; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableSerializable; +import edu.wpi.first.util.sendable2.SendableTable; /** * Analog channel class. @@ -26,7 +26,7 @@ * accumulated effectively increasing the resolution, while the averaged samples are divided by the * number of samples to retain the resolution, but get more stable values. */ -public class AnalogInput implements Sendable, AutoCloseable { +public class AnalogInput implements SendableSerializable, AutoCloseable { private static final int kAccumulatorSlot = 1; int m_port; // explicit no modifier, private and package accessible. private int m_channel; @@ -47,12 +47,10 @@ public AnalogInput(final int channel) { m_port = AnalogJNI.initializeAnalogInputPort(portHandle); HAL.report(tResourceType.kResourceType_AnalogChannel, channel + 1); - SendableRegistry.addLW(this, "AnalogInput", channel); } @Override public void close() { - SendableRegistry.remove(this); AnalogJNI.freeAnalogInputPort(m_port); m_port = 0; m_channel = 0; @@ -334,9 +332,28 @@ public void setSimDevice(SimDevice device) { AnalogJNI.setAnalogInputSimDevice(m_port, device.getNativeHandle()); } - @Override - public void initSendable(SendableBuilder builder) { - builder.setSmartDashboardType("Analog Input"); - builder.addDoubleProperty("Value", this::getAverageVoltage, null); + public static class AnalogInputSendable implements Sendable { + @Override + public Class getTypeClass() { + return AnalogInput.class; + } + + @Override + public String getTypeString() { + return "Analog Input"; + } + + @Override + public boolean isClosed(AnalogInput obj) { + return obj.m_port == 0; + } + + @Override + public void initSendable(AnalogInput obj, SendableTable table) { + table.publishDouble("Value", obj::getAverageVoltage); + } } + + /** Sendable for serialization. */ + public static final AnalogInputSendable sendable = new AnalogInputSendable(); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogTrigger.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogTrigger.java index 1d4a404dd95..e2e5e4fd7dd 100644 --- a/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogTrigger.java +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/AnalogTrigger.java @@ -8,14 +8,14 @@ import edu.wpi.first.hal.FRCNetComm.tResourceType; import edu.wpi.first.hal.HAL; import edu.wpi.first.hal.util.BoundaryException; -import edu.wpi.first.util.sendable.Sendable; -import edu.wpi.first.util.sendable.SendableBuilder; -import edu.wpi.first.util.sendable.SendableRegistry; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableSerializable; +import edu.wpi.first.util.sendable2.SendableTable; import edu.wpi.first.wpilibj.AnalogTriggerOutput.AnalogTriggerType; import java.lang.ref.Reference; /** Class for creating and configuring Analog Triggers. */ -public class AnalogTrigger implements Sendable, AutoCloseable { +public class AnalogTrigger implements SendableSerializable, AutoCloseable { /** Where the analog trigger is attached. */ protected int m_port; @@ -34,7 +34,6 @@ public class AnalogTrigger implements Sendable, AutoCloseable { public AnalogTrigger(final int channel) { this(new AnalogInput(channel)); m_ownsAnalog = true; - SendableRegistry.addChild(this, m_analogInput); } /** @@ -52,7 +51,6 @@ public AnalogTrigger(AnalogInput channel) { int index = getIndex(); HAL.report(tResourceType.kResourceType_AnalogTrigger, index + 1); - SendableRegistry.addLW(this, "AnalogTrigger", index); } /** @@ -69,12 +67,10 @@ public AnalogTrigger(DutyCycle input) { int index = getIndex(); HAL.report(tResourceType.kResourceType_AnalogTrigger, index + 1); - SendableRegistry.addLW(this, "AnalogTrigger", index); } @Override public void close() { - SendableRegistry.remove(this); AnalogJNI.cleanAnalogTrigger(m_port); m_port = 0; if (m_ownsAnalog && m_analogInput != null) { @@ -186,10 +182,30 @@ public AnalogTriggerOutput createOutput(AnalogTriggerType type) { return new AnalogTriggerOutput(this, type); } - @Override - public void initSendable(SendableBuilder builder) { - if (m_ownsAnalog) { - m_analogInput.initSendable(builder); + public static class AnalogTriggerSendable implements Sendable { + @Override + public Class getTypeClass() { + return AnalogTrigger.class; + } + + @Override + public String getTypeString() { + return "Analog Input"; + } + + @Override + public boolean isClosed(AnalogTrigger obj) { + return obj.m_port == 0; + } + + @Override + public void initSendable(AnalogTrigger obj, SendableTable table) { + if (obj.m_ownsAnalog) { + AnalogInput.sendable.initSendable(obj.m_analogInput, table); + } } } + + /** Sendable for serialization. */ + public static final AnalogTriggerSendable sendable = new AnalogTriggerSendable(); } diff --git a/wpilibj/src/main/java/edu/wpi/first/wpilibj/telemetry/Telemetry.java b/wpilibj/src/main/java/edu/wpi/first/wpilibj/telemetry/Telemetry.java new file mode 100644 index 00000000000..a10bdca5059 --- /dev/null +++ b/wpilibj/src/main/java/edu/wpi/first/wpilibj/telemetry/Telemetry.java @@ -0,0 +1,415 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.wpilibj.telemetry; + +import edu.wpi.first.networktables.NetworkSendableTable; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.util.function.BooleanConsumer; +import edu.wpi.first.util.function.FloatConsumer; +import edu.wpi.first.util.function.FloatSupplier; +import edu.wpi.first.util.protobuf.Protobuf; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableOption; +import edu.wpi.first.util.sendable2.SendableTable; +import edu.wpi.first.util.struct.Struct; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleSupplier; +import java.util.function.LongConsumer; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +/** Helper class for building Sendable dashboard representations. */ +public final class Telemetry { + /** The {@link SendableTable} used by {@link Telemetry}. */ + private static SendableTable table; + + static { + table = new NetworkSendableTable(NetworkTableInstance.getDefault(), "/telemetry"); + } + + private Telemetry() { + throw new UnsupportedOperationException("This is a utility class!"); + } + + public static void setTable(SendableTable table) { + Telemetry.table = table; + } + + public static SendableTable getTable() { + return table; + } + + public static boolean getBoolean(String name, boolean defaultValue) { + return getTable().getBoolean(name, defaultValue); + } + + public static long getInteger(String name, long defaultValue) { + return getTable().getInteger(name, defaultValue); + } + + public static float getFloat(String name, float defaultValue) { + return getTable().getFloat(name, defaultValue); + } + + public static double getDouble(String name, double defaultValue) { + return getTable().getDouble(name, defaultValue); + } + + public static String getString(String name, String defaultValue) { + return getTable().getString(name, defaultValue); + } + + public static boolean[] getBooleanArray(String name, boolean[] defaultValue) { + return getTable().getBooleanArray(name, defaultValue); + } + + public static long[] getIntegerArray(String name, long[] defaultValue) { + return getTable().getIntegerArray(name, defaultValue); + } + + public static float[] getFloatArray(String name, float[] defaultValue) { + return getTable().getFloatArray(name, defaultValue); + } + + public static double[] getDoubleArray(String name, double[] defaultValue) { + return getTable().getDoubleArray(name, defaultValue); + } + + public static String[] getStringArray(String name, String[] defaultValue) { + return getTable().getStringArray(name, defaultValue); + } + + public static byte[] getRaw(String name, String typeString, byte[] defaultValue) { + return getTable().getRaw(name, typeString, defaultValue); + } + + public static T getStruct(String name, Struct struct, T defaultValue) { + return getTable().getStruct(name, struct, defaultValue); + } + + public static boolean getStructInto(String name, T out, Struct struct) { + return getTable().getStructInto(name, out, struct); + } + + public static T getProtobuf(String name, Protobuf proto, T defaultValue) { + return getTable().getProtobuf(name, proto, defaultValue); + } + + public static boolean getProtobufInto(String name, T out, Protobuf proto) { + return getTable().getProtobufInto(name, out, proto); + } + + public static void setBoolean(String name, boolean value) { + getTable().setBoolean(name, value); + } + + public static void setInteger(String name, long value) { + getTable().setInteger(name, value); + } + + public static void setFloat(String name, float value) { + getTable().setFloat(name, value); + } + + public static void setDouble(String name, double value) { + getTable().setDouble(name, value); + } + + public static void setString(String name, String value) { + getTable().setString(name, value); + } + + public static void setBooleanArray(String name, boolean[] value) { + getTable().setBooleanArray(name, value); + } + + public static void setIntegerArray(String name, long[] value) { + getTable().setIntegerArray(name, value); + } + + public static void setFloatArray(String name, float[] value) { + getTable().setFloatArray(name, value); + } + + public static void setDoubleArray(String name, double[] value) { + getTable().setDoubleArray(name, value); + } + + public static void setStringArray(String name, String[] value) { + getTable().setStringArray(name, value); + } + + public static void setRaw(String name, String typeString, byte[] value, int start, int len) { + getTable().setRaw(name, typeString, value, start, len); + } + + public static void setRaw(String name, String typeString, ByteBuffer value, int start, int len) { + getTable().setRaw(name, typeString, value, start, len); + } + + public static void setStruct(String name, T value, Struct struct) { + getTable().setStruct(name, value, struct); + } + + public static void setProtobuf(String name, T value, Protobuf proto) { + getTable().setProtobuf(name, value, proto); + } + + public static void publishBoolean(String name, BooleanSupplier supplier) { + getTable().publishBoolean(name, supplier); + } + + public static void publishInteger(String name, LongSupplier supplier) { + getTable().publishInteger(name, supplier); + } + + public static void publishFloat(String name, FloatSupplier supplier) { + getTable().publishFloat(name, supplier); + } + + public static void publishDouble(String name, DoubleSupplier supplier) { + getTable().publishDouble(name, supplier); + } + + public static void publishString(String name, Supplier supplier) { + getTable().publishString(name, supplier); + } + + public static void publishBooleanArray(String name, Supplier supplier) { + getTable().publishBooleanArray(name, supplier); + } + + public static void publishIntegerArray(String name, Supplier supplier) { + getTable().publishIntegerArray(name, supplier); + } + + public static void publishFloatArray(String name, Supplier supplier) { + getTable().publishFloatArray(name, supplier); + } + + public static void publishDoubleArray(String name, Supplier supplier) { + getTable().publishDoubleArray(name, supplier); + } + + public static void publishStringArray(String name, Supplier supplier) { + getTable().publishStringArray(name, supplier); + } + + public static void publishRawBytes(String name, String typeString, Supplier supplier) { + getTable().publishRawBytes(name, typeString, supplier); + } + + public static void publishRawBuffer( + String name, String typeString, Supplier supplier) { + getTable().publishRawBuffer(name, typeString, supplier); + } + + public static void publishStruct(String name, Struct struct, Supplier supplier) { + getTable().publishStruct(name, struct, supplier); + } + + public static void publishProtobuf(String name, Protobuf proto, Supplier supplier) { + getTable().publishProtobuf(name, proto, supplier); + } + + public static BooleanConsumer addBooleanPublisher(String name) { + return getTable().addBooleanPublisher(name); + } + + public static LongConsumer addIntegerPublisher(String name) { + return getTable().addIntegerPublisher(name); + } + + public static FloatConsumer addFloatPublisher(String name) { + return getTable().addFloatPublisher(name); + } + + public static DoubleConsumer addDoublePublisher(String name) { + return getTable().addDoublePublisher(name); + } + + public static Consumer addStringPublisher(String name) { + return getTable().addStringPublisher(name); + } + + public static Consumer addBooleanArrayPublisher(String name) { + return getTable().addBooleanArrayPublisher(name); + } + + public static Consumer addIntegerArrayPublisher(String name) { + return getTable().addIntegerArrayPublisher(name); + } + + public static Consumer addFloatArrayPublisher(String name) { + return getTable().addFloatArrayPublisher(name); + } + + public static Consumer addDoubleArrayPublisher(String name) { + return getTable().addDoubleArrayPublisher(name); + } + + public static Consumer addStringArrayPublisher(String name) { + return getTable().addStringArrayPublisher(name); + } + + public static Consumer addRawBytesPublisher(String name, String typeString) { + return getTable().addRawBytesPublisher(name, typeString); + } + + public static Consumer addRawBufferPublisher(String name, String typeString) { + return getTable().addRawBufferPublisher(name, typeString); + } + + public static Consumer addStructPublisher(String name, Struct struct) { + return getTable().addStructPublisher(name, struct); + } + + public static Consumer addProtobufPublisher(String name, Protobuf proto) { + return getTable().addProtobufPublisher(name, proto); + } + + public static void subscribeBoolean(String name, BooleanConsumer consumer) { + getTable().subscribeBoolean(name, consumer); + } + + public static void subscribeInteger(String name, LongConsumer consumer) { + getTable().subscribeInteger(name, consumer); + } + + public static void subscribeFloat(String name, FloatConsumer consumer) { + getTable().subscribeFloat(name, consumer); + } + + public static void subscribeDouble(String name, DoubleConsumer consumer) { + getTable().subscribeDouble(name, consumer); + } + + public static void subscribeString(String name, Consumer consumer) { + getTable().subscribeString(name, consumer); + } + + public static void subscribeBooleanArray(String name, Consumer consumer) { + getTable().subscribeBooleanArray(name, consumer); + } + + public static void subscribeIntegerArray(String name, Consumer consumer) { + getTable().subscribeIntegerArray(name, consumer); + } + + public static void subscribeFloatArray(String name, Consumer consumer) { + getTable().subscribeFloatArray(name, consumer); + } + + public static void subscribeDoubleArray(String name, Consumer consumer) { + getTable().subscribeDoubleArray(name, consumer); + } + + public static void subscribeStringArray(String name, Consumer consumer) { + getTable().subscribeStringArray(name, consumer); + } + + public static void subscribeRawBytes(String name, String typeString, Consumer consumer) { + getTable().subscribeRawBytes(name, typeString, consumer); + } + + public static void subscribeStruct(String name, Struct struct, Consumer consumer) { + getTable().subscribeStruct(name, struct, consumer); + } + + public static void subscribeProtobuf( + String name, Protobuf proto, Consumer consumer) { + getTable().subscribeProtobuf(name, proto, consumer); + } + + public static SendableTable addSendable(String name, T obj, Sendable sendable) { + return getTable().addSendable(name, obj, sendable); + } + + public static SendableTable getChild(String name) { + return getTable().getChild(name); + } + + public static void setPublishOptions(String name, SendableOption... options) { + getTable().setPublishOptions(name, options); + } + + public static void setSubscribeOptions(String name, SendableOption... options) { + getTable().setSubscribeOptions(name, options); + } + + /** + * Gets the current value of a property (as a JSON string). + * + * @param name name + * @param propName property name + * @return JSON string; "null" if the property does not exist. + */ + public static String getProperty(String name, String propName) { + return getTable().getProperty(name, propName); + } + + /** + * Sets a property value. + * + * @param name name + * @param propName property name + * @param value property value (JSON string) + * @throws IllegalArgumentException if properties is not parseable as JSON + */ + public static void setProperty(String name, String propName, String value) { + getTable().setProperty(name, propName, value); + } + + /** + * Deletes a property. Has no effect if the property does not exist. + * + * @param name name + * @param propName property name + */ + public static void deleteProperty(String name, String propName) { + getTable().deleteProperty(name, propName); + } + + /** + * Gets all topic properties as a JSON object string. Each key in the object is the property name, + * and the corresponding value is the property value. + * + * @param name name + * @return JSON string + */ + public static String getProperties(String name) { + return getTable().getProperties(name); + } + + /** + * Updates multiple topic properties. Each key in the passed-in object is the name of the property + * to add/update, and the corresponding value is the property value to set for that property. Null + * values result in deletion of the corresponding property. + * + * @param name name + * @param properties map of keys/JSON values to add/update/delete + * @throws IllegalArgumentException if properties is not a JSON object + */ + public static void setProperties(String name, Map properties) { + getTable().setProperties(name, properties); + } + + public static void remove(String name) { + getTable().remove(name); + } + + public static void addPeriodic(Runnable runnable) { + getTable().addPeriodic(runnable); + } + + /** Erases all publishers and subscribers. */ + public static void clear() { + getTable().clear(); + } +} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLogSendableTable.java b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLogSendableTable.java new file mode 100644 index 00000000000..2e1dd6fd0f4 --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/datalog/DataLogSendableTable.java @@ -0,0 +1,1368 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.datalog; + +import edu.wpi.first.util.function.BooleanConsumer; +import edu.wpi.first.util.function.FloatConsumer; +import edu.wpi.first.util.function.FloatSupplier; +import edu.wpi.first.util.protobuf.Protobuf; +import edu.wpi.first.util.protobuf.ProtobufBuffer; +import edu.wpi.first.util.sendable2.Sendable; +import edu.wpi.first.util.sendable2.SendableOption; +import edu.wpi.first.util.sendable2.SendableTable; +import edu.wpi.first.util.struct.Struct; +import edu.wpi.first.util.struct.StructBuffer; +import java.io.IOException; +import java.lang.ref.WeakReference; +import java.nio.ByteBuffer; +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.atomic.AtomicReference; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleSupplier; +import java.util.function.LongConsumer; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +/** DataLog-backed implementation of SendableTable. */ +public class DataLogSendableTable implements SendableTable { + private static final char PATH_SEPARATOR = '/'; + + private final String m_path; + private final String m_pathWithSep; + private final DataLog m_log; + + private static class EntryData { + EntryData(DataLog log, String path) { + m_log = log; + m_path = path; + } + + synchronized void close() { + if (m_entry != null) { + m_entry.finish(); + m_entry = null; + } + } + + synchronized void refreshProperties() { + StringBuilder sb = new StringBuilder(); + sb.append('{'); + m_propertiesMap.forEach( + (k, v) -> { + sb.append('"'); + sb.append(k.replace("\"", "\\\"")); + sb.append("\":"); + sb.append(v); + sb.append(','); + }); + // replace the trailing comma with a } + sb.setCharAt(sb.length() - 1, '}'); + m_properties = sb.toString(); + if (m_entry != null) { + m_entry.setMetadata(m_properties); + } + } + + synchronized BooleanLogEntry initBoolean() { + if (m_entry == null) { + BooleanLogEntry entry = new BooleanLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof BooleanLogEntry) { + return (BooleanLogEntry) m_entry; + } else { + return null; + } + } + + synchronized IntegerLogEntry initInteger() { + if (m_entry == null) { + IntegerLogEntry entry = new IntegerLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof IntegerLogEntry) { + return (IntegerLogEntry) m_entry; + } else { + return null; + } + } + + synchronized FloatLogEntry initFloat() { + if (m_entry == null) { + FloatLogEntry entry = new FloatLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof FloatLogEntry) { + return (FloatLogEntry) m_entry; + } else { + return null; + } + } + + synchronized DoubleLogEntry initDouble() { + if (m_entry == null) { + DoubleLogEntry entry = new DoubleLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof DoubleLogEntry) { + return (DoubleLogEntry) m_entry; + } else { + return null; + } + } + + synchronized StringLogEntry initString() { + if (m_entry == null) { + StringLogEntry entry = new StringLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof StringLogEntry) { + return (StringLogEntry) m_entry; + } else { + return null; + } + } + + synchronized BooleanArrayLogEntry initBooleanArray() { + if (m_entry == null) { + BooleanArrayLogEntry entry = new BooleanArrayLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof BooleanArrayLogEntry) { + return (BooleanArrayLogEntry) m_entry; + } else { + return null; + } + } + + synchronized IntegerArrayLogEntry initIntegerArray() { + if (m_entry == null) { + IntegerArrayLogEntry entry = new IntegerArrayLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof IntegerArrayLogEntry) { + return (IntegerArrayLogEntry) m_entry; + } else { + return null; + } + } + + synchronized FloatArrayLogEntry initFloatArray() { + if (m_entry == null) { + FloatArrayLogEntry entry = new FloatArrayLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof FloatArrayLogEntry) { + return (FloatArrayLogEntry) m_entry; + } else { + return null; + } + } + + synchronized DoubleArrayLogEntry initDoubleArray() { + if (m_entry == null) { + DoubleArrayLogEntry entry = new DoubleArrayLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof DoubleArrayLogEntry) { + return (DoubleArrayLogEntry) m_entry; + } else { + return null; + } + } + + synchronized StringArrayLogEntry initStringArray() { + if (m_entry == null) { + StringArrayLogEntry entry = new StringArrayLogEntry(m_log, m_path, m_properties); + m_entry = entry; + return entry; + } else if (m_entry instanceof StringArrayLogEntry) { + return (StringArrayLogEntry) m_entry; + } else { + return null; + } + } + + synchronized RawLogEntry initRaw(String typeString) { + if (m_entry == null) { + RawLogEntry entry = new RawLogEntry(m_log, m_path, m_properties, typeString); + m_entry = entry; + m_typeString = typeString; + return entry; + } else if (m_entry instanceof RawLogEntry && typeString.equals(m_typeString)) { + return (RawLogEntry) m_entry; + } else { + return null; + } + } + + synchronized StructLogEntry initStruct(Struct struct) { + if (m_entry == null) { + StructLogEntry entry = StructLogEntry.create(m_log, m_path, struct, m_properties); + m_entry = entry; + m_structBuffer = StructBuffer.create(struct); + m_log.addSchema(struct); + return entry; + } else if (m_structBuffer != null && m_structBuffer.getStruct().equals(struct)) { + @SuppressWarnings("unchecked") + StructLogEntry entry = (StructLogEntry) m_entry; + return entry; + } else { + return null; + } + } + + synchronized ProtobufLogEntry initProtobuf(Protobuf proto) { + if (m_entry == null) { + ProtobufLogEntry entry = ProtobufLogEntry.create(m_log, m_path, proto, m_properties); + m_entry = entry; + m_protobufBuffer = ProtobufBuffer.create(proto); + m_log.addSchema(proto); + return entry; + } else if (m_protobufBuffer != null && m_protobufBuffer.getProto().equals(proto)) { + @SuppressWarnings("unchecked") + ProtobufLogEntry entry = (ProtobufLogEntry) m_entry; + return entry; + } else { + return null; + } + } + + void setBoolean(boolean value) { + BooleanLogEntry entry = (BooleanLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setInteger(long value) { + IntegerLogEntry entry = (IntegerLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setFloat(float value) { + FloatLogEntry entry = (FloatLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setDouble(double value) { + DoubleLogEntry entry = (DoubleLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setString(String value) { + StringLogEntry entry = (StringLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setBooleanArray(boolean[] value) { + BooleanArrayLogEntry entry = (BooleanArrayLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setIntegerArray(long[] value) { + IntegerArrayLogEntry entry = (IntegerArrayLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setFloatArray(float[] value) { + FloatArrayLogEntry entry = (FloatArrayLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setDoubleArray(double[] value) { + DoubleArrayLogEntry entry = (DoubleArrayLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setStringArray(String[] value) { + StringArrayLogEntry entry = (StringArrayLogEntry) m_entry; + if (m_appendAll) { + entry.append(value); + } else { + entry.update(value); + } + } + + void setRaw(byte[] value) { + setRaw(value, 0, value.length); + } + + void setRaw(byte[] value, int start, int len) { + RawLogEntry entry = (RawLogEntry) m_entry; + if (m_appendAll) { + entry.append(value, start, len); + } else { + entry.update(value, start, len); + } + } + + void setRaw(ByteBuffer value) { + int pos = value.position(); + setRaw(value, pos, value.limit() - pos); + } + + void setRaw(ByteBuffer value, int start, int len) { + RawLogEntry entry = (RawLogEntry) m_entry; + if (m_appendAll) { + entry.append(value, start, len); + } else { + entry.update(value, start, len); + } + } + + void setPolledUpdate(Consumer consumer) { + m_polledUpdate.set(consumer); + } + + Consumer getPolledUpdate() { + return m_polledUpdate.get(); + } + + final DataLog m_log; + final String m_path; + final Map m_propertiesMap = new HashMap<>(); + final AtomicReference> m_polledUpdate = new AtomicReference<>(); + + DataLogEntry m_entry; + String m_typeString; + + String m_properties = "{}"; + StructBuffer m_structBuffer; + ProtobufBuffer m_protobufBuffer; + + boolean m_appendAll; + } + + private final ConcurrentMap m_entries = new ConcurrentHashMap<>(); + private final ConcurrentMap m_tables = new ConcurrentHashMap<>(); + private Consumer m_closeSendable; + private boolean m_closed; + + private T get(String name, Class cls) { + EntryData data = m_entries.get(name); + if (data == null || data.m_entry == null || data.m_entry.getClass() != cls) { + return null; + } + @SuppressWarnings("unchecked") + T entry = (T) data.m_entry; + return entry; + } + + private EntryData getOrNew(String name) { + return m_entries.computeIfAbsent(name, k -> new EntryData(m_log, m_pathWithSep + name)); + } + + /** + * Constructs a DataLog-backed sendable table. + * + * @param log DataLog + * @param path path to sendable table, excluding trailing '/' + */ + public DataLogSendableTable(DataLog log, String path) { + m_path = path; + m_pathWithSep = path + PATH_SEPARATOR; + m_log = log; + } + + /** + * Gets the DataLog for the table. + * + * @return DataLog + */ + public DataLog getLog() { + return m_log; + } + + @Override + public String toString() { + return "DataLogSendableTable: " + m_path; + } + + @Override + public boolean getBoolean(String name, boolean defaultValue) { + BooleanLogEntry entry = get(name, BooleanLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public long getInteger(String name, long defaultValue) { + IntegerLogEntry entry = get(name, IntegerLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public float getFloat(String name, float defaultValue) { + FloatLogEntry entry = get(name, FloatLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public double getDouble(String name, double defaultValue) { + DoubleLogEntry entry = get(name, DoubleLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public String getString(String name, String defaultValue) { + StringLogEntry entry = get(name, StringLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public boolean[] getBooleanArray(String name, boolean[] defaultValue) { + BooleanArrayLogEntry entry = get(name, BooleanArrayLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public long[] getIntegerArray(String name, long[] defaultValue) { + IntegerArrayLogEntry entry = get(name, IntegerArrayLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public float[] getFloatArray(String name, float[] defaultValue) { + FloatArrayLogEntry entry = get(name, FloatArrayLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public double[] getDoubleArray(String name, double[] defaultValue) { + DoubleArrayLogEntry entry = get(name, DoubleArrayLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public String[] getStringArray(String name, String[] defaultValue) { + StringArrayLogEntry entry = get(name, StringArrayLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public byte[] getRaw(String name, String typeString, byte[] defaultValue) { + RawLogEntry entry = get(name, RawLogEntry.class); + if (entry == null) { + return defaultValue; + } + return entry.getLastValue(); + } + + @Override + public T getStruct(String name, Struct struct, T defaultValue) { + EntryData data = m_entries.get(name); + if (data == null || data.m_structBuffer == null || !data.m_structBuffer.getStruct().equals(struct)) { + return defaultValue; + } + @SuppressWarnings("unchecked") + StructLogEntry entry = (StructLogEntry) data.m_entry; + return entry.getLastValue(); + } + + @Override + public boolean getStructInto(String name, T out, Struct struct) { + return false; // TODO + } + + @Override + public T getProtobuf(String name, Protobuf proto, T defaultValue) { + EntryData data = m_entries.get(name); + if (data == null || data.m_protobufBuffer == null || !data.m_protobufBuffer.getProto().equals(proto)) { + return defaultValue; + } + @SuppressWarnings("unchecked") + ProtobufLogEntry entry = (ProtobufLogEntry) data.m_entry; + return entry.getLastValue(); + } + + @Override + public boolean getProtobufInto(String name, T out, Protobuf proto) { + return false; // TODO + } + + @Override + public void setBoolean(String name, boolean value) { + EntryData data = getOrNew(name); + if (data.initBoolean() != null) { + data.setBoolean(value); + } + } + + @Override + public void setInteger(String name, long value) { + EntryData data = getOrNew(name); + if (data.initInteger() != null) { + data.setInteger(value); + } + } + + @Override + public void setFloat(String name, float value) { + EntryData data = getOrNew(name); + if (data.initFloat() != null) { + data.setFloat(value); + } + } + + @Override + public void setDouble(String name, double value) { + EntryData data = getOrNew(name); + if (data.initDouble() != null) { + data.setDouble(value); + } + } + + @Override + public void setString(String name, String value) { + EntryData data = getOrNew(name); + if (data.initString() != null) { + data.setString(value); + } + } + + @Override + public void setBooleanArray(String name, boolean[] value) { + EntryData data = getOrNew(name); + if (data.initBooleanArray() != null) { + data.setBooleanArray(value); + } + } + + @Override + public void setIntegerArray(String name, long[] value) { + EntryData data = getOrNew(name); + if (data.initIntegerArray() != null) { + data.setIntegerArray(value); + } + } + + @Override + public void setFloatArray(String name, float[] value) { + EntryData data = getOrNew(name); + if (data.initFloatArray() != null) { + data.setFloatArray(value); + } + } + + @Override + public void setDoubleArray(String name, double[] value) { + EntryData data = getOrNew(name); + if (data.initDoubleArray() != null) { + data.setDoubleArray(value); + } + } + + @Override + public void setStringArray(String name, String[] value) { + EntryData data = getOrNew(name); + if (data.initStringArray() != null) { + data.setStringArray(value); + } + } + + @Override + public void setRaw(String name, String typeString, byte[] value, int start, int len) { + EntryData data = getOrNew(name); + if (data.initRaw(typeString) != null) { + data.setRaw(value, start, len); + } + } + + @Override + public void setRaw(String name, String typeString, ByteBuffer value, int start, int len) { + EntryData data = getOrNew(name); + if (data.initRaw(typeString) != null) { + data.setRaw(value, start, len); + } + } + + @Override + public void setStruct(String name, T value, Struct struct) { + EntryData data = getOrNew(name); + if (data.initStruct(struct) != null) { + synchronized (data) { + @SuppressWarnings("unchecked") + StructBuffer buf = (StructBuffer) data.m_structBuffer; + ByteBuffer bb = buf.write(value); + data.setRaw(bb, 0, bb.position()); + } + } + } + + @Override + public void setProtobuf(String name, T value, Protobuf proto) { + EntryData data = getOrNew(name); + if (data.initProtobuf(proto) != null) { + synchronized (data) { + @SuppressWarnings("unchecked") + ProtobufBuffer buf = (ProtobufBuffer) data.m_protobufBuffer; + try { + ByteBuffer bb = buf.write(value); + data.setRaw(bb, 0, bb.position()); + } catch (IOException e) { + // ignore + } + } + } + } + + @Override + public void publishBoolean(String name, BooleanSupplier supplier) { + EntryData data = getOrNew(name); + if (data.initBoolean() != null) { + data.setPolledUpdate( + entry -> { + entry.setBoolean(supplier.getAsBoolean()); + }); + } + } + + @Override + public void publishInteger(String name, LongSupplier supplier) { + EntryData data = getOrNew(name); + if (data.initInteger() != null) { + data.setPolledUpdate( + entry -> { + entry.setInteger(supplier.getAsLong()); + }); + } + } + + @Override + public void publishFloat(String name, FloatSupplier supplier) { + EntryData data = getOrNew(name); + if (data.initFloat() != null) { + data.setPolledUpdate( + entry -> { + entry.setFloat(supplier.getAsFloat()); + }); + } + } + + @Override + public void publishDouble(String name, DoubleSupplier supplier) { + EntryData data = getOrNew(name); + if (data.initDouble() != null) { + data.setPolledUpdate( + entry -> { + entry.setDouble(supplier.getAsDouble()); + }); + } + } + + @Override + public void publishString(String name, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initString() != null) { + data.setPolledUpdate( + entry -> { + entry.setString(supplier.get()); + }); + } + } + + @Override + public void publishBooleanArray(String name, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initBooleanArray() != null) { + data.setPolledUpdate( + entry -> { + entry.setBooleanArray(supplier.get()); + }); + } + } + + @Override + public void publishIntegerArray(String name, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initIntegerArray() != null) { + data.setPolledUpdate( + entry -> { + entry.setIntegerArray(supplier.get()); + }); + } + } + + @Override + public void publishFloatArray(String name, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initFloatArray() != null) { + data.setPolledUpdate( + entry -> { + entry.setFloatArray(supplier.get()); + }); + } + } + + @Override + public void publishDoubleArray(String name, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initDoubleArray() != null) { + data.setPolledUpdate( + entry -> { + entry.setDoubleArray(supplier.get()); + }); + } + } + + @Override + public void publishStringArray(String name, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initStringArray() != null) { + data.setPolledUpdate( + entry -> { + entry.setStringArray(supplier.get()); + }); + } + } + + @Override + public void publishRawBytes(String name, String typeString, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initRaw(typeString) != null) { + data.setPolledUpdate( + entry -> { + entry.setRaw(supplier.get()); + }); + } + } + + @Override + public void publishRawBuffer(String name, String typeString, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initRaw(typeString) != null) { + data.setPolledUpdate( + entry -> { + entry.setRaw(supplier.get()); + }); + } + } + + @Override + public void publishStruct(String name, Struct struct, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initRaw(struct.getTypeString()) != null) { + addSchema(struct); + final StructBuffer buf = StructBuffer.create(struct); + data.setPolledUpdate( + entry -> { + entry.setRaw(buf.write(supplier.get())); + }); + } + } + + @Override + public void publishProtobuf(String name, Protobuf proto, Supplier supplier) { + EntryData data = getOrNew(name); + if (data.initRaw(proto.getTypeString()) != null) { + addSchema(proto); + ProtobufBuffer buf = ProtobufBuffer.create(proto); + data.setPolledUpdate( + entry -> { + try { + entry.setRaw(buf.write(supplier.get())); + } catch (IOException e) { + return; // ignore + } + }); + } + } + + @Override + public BooleanConsumer addBooleanPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initBoolean() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setBoolean(value); + } + }; + } + + @Override + public LongConsumer addIntegerPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initInteger() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setInteger(value); + } + }; + } + + @Override + public FloatConsumer addFloatPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initFloat() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setFloat(value); + } + }; + } + + @Override + public DoubleConsumer addDoublePublisher(String name) { + EntryData data = getOrNew(name); + if (data.initDouble() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setDouble(value); + } + }; + } + + @Override + public Consumer addStringPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initString() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setString(value); + } + }; + } + + @Override + public Consumer addBooleanArrayPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initBooleanArray() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setBooleanArray(value); + } + }; + } + + @Override + public Consumer addIntegerArrayPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initIntegerArray() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setIntegerArray(value); + } + }; + } + + @Override + public Consumer addFloatArrayPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initFloatArray() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setFloatArray(value); + } + }; + } + + @Override + public Consumer addDoubleArrayPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initDoubleArray() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setDoubleArray(value); + } + }; + } + + @Override + public Consumer addStringArrayPublisher(String name) { + EntryData data = getOrNew(name); + if (data.initStringArray() == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setStringArray(value); + } + }; + } + + @Override + public Consumer addRawBytesPublisher(String name, String typeString) { + EntryData data = getOrNew(name); + if (data.initRaw(typeString) == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setRaw(value); + } + }; + } + + @Override + public Consumer addRawBufferPublisher(String name, String typeString) { + EntryData data = getOrNew(name); + if (data.initRaw(typeString) == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + d.setRaw(value); + } + }; + } + + @Override + public Consumer addStructPublisher(String name, Struct struct) { + EntryData data = getOrNew(name); + if (data.initStruct(struct) == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + final StructBuffer buf = StructBuffer.create(struct); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + ByteBuffer bb = buf.write(value); + data.setRaw(bb, 0, bb.position()); + } + }; + } + + @Override + public Consumer addProtobufPublisher(String name, Protobuf proto) { + EntryData data = getOrNew(name); + if (data.initProtobuf(proto) == null) { + return value -> {}; + } + final WeakReference dataRef = new WeakReference<>(data); + final ProtobufBuffer buf = ProtobufBuffer.create(proto); + return value -> { + EntryData d = dataRef.get(); + if (d != null && d.m_entry != null) { + try { + ByteBuffer bb = buf.write(value); + data.setRaw(bb, 0, bb.position()); + } catch (IOException e) { + // ignore + } + } + }; + } + + @Override + public void subscribeBoolean(String name, BooleanConsumer consumer) {} + + @Override + public void subscribeInteger(String name, LongConsumer consumer) {} + + @Override + public void subscribeFloat(String name, FloatConsumer consumer) {} + + @Override + public void subscribeDouble(String name, DoubleConsumer consumer) {} + + @Override + public void subscribeString(String name, Consumer consumer) {} + + @Override + public void subscribeBooleanArray(String name, Consumer consumer) {} + + @Override + public void subscribeIntegerArray(String name, Consumer consumer) {} + + @Override + public void subscribeFloatArray(String name, Consumer consumer) {} + + @Override + public void subscribeDoubleArray(String name, Consumer consumer) {} + + @Override + public void subscribeStringArray(String name, Consumer consumer) {} + + @Override + public void subscribeRawBytes(String name, String typeString, Consumer consumer) {} + + @Override + public DataLogSendableTable addSendable(String name, T obj, Sendable sendable) { + DataLogSendableTable child = getChild(name); + if (child.m_closeSendable == null) { + sendable.initSendable(obj, child); + child.m_closeSendable = + table -> { + sendable.closeSendable(obj, table); + }; + } + return child; + } + + @SuppressWarnings("resource") + @Override + public DataLogSendableTable getChild(String name) { + return m_tables.computeIfAbsent( + name, k -> new DataLogSendableTable(m_log, m_pathWithSep + name)); + } + + @Override + public void setPublishOptions(String name, SendableOption... options) { + EntryData data = getOrNew(name); + for (SendableOption option : options) { + if (option.getKind() == SendableOption.Kind.kKeepDuplicates) { + data.m_appendAll = option.getBooleanValue(); + } + } + } + + @Override + public void setSubscribeOptions(String name, SendableOption... options) {} + + /** + * Gets the current value of a property (as a JSON string). + * + * @param name name + * @param propName property name + * @return JSON string; "null" if the property does not exist. + */ + @Override + public String getProperty(String name, String propName) { + EntryData data = m_entries.get(name); + if (data == null) { + return "null"; + } + String value; + synchronized (data) { + value = data.m_propertiesMap.get(propName); + } + return value != null ? value : "null"; + } + + /** + * Sets a property value. + * + * @param name name + * @param propName property name + * @param value property value (JSON string) + * @throws IllegalArgumentException if properties is not parseable as JSON + */ + @Override + public void setProperty(String name, String propName, String value) { + EntryData data = m_entries.computeIfAbsent(name, k -> new EntryData(m_log, name)); + synchronized (data) { + data.m_propertiesMap.put(propName, value); + data.refreshProperties(); + } + } + + /** + * Deletes a property. Has no effect if the property does not exist. + * + * @param name name + * @param propName property name + */ + @Override + public void deleteProperty(String name, String propName) { + EntryData data = m_entries.get(name); + if (data == null) { + return; + } + synchronized (data) { + if (data.m_propertiesMap.remove(propName) != null) { + data.refreshProperties(); + } + } + } + + /** + * Gets all topic properties as a JSON object string. Each key in the object is the property name, + * and the corresponding value is the property value. + * + * @param name name + * @return JSON string + */ + @Override + public String getProperties(String name) { + EntryData data = m_entries.get(name); + if (data == null) { + return "{}"; + } + synchronized (data) { + return data.m_properties; + } + } + + /** + * Updates multiple topic properties. Each key in the passed-in object is the name of the property + * to add/update, and the corresponding value is the property value to set for that property. Null + * values result in deletion of the corresponding property. + * + * @param name name + * @param properties map of keys/JSON values to add/update/delete + * @throws IllegalArgumentException if properties is not a JSON object + */ + @Override + public void setProperties(String name, Map properties) { + if (properties.isEmpty()) { + return; + } + EntryData data = m_entries.computeIfAbsent(name, k -> new EntryData(m_log, name)); + synchronized (data) { + properties.forEach( + (k, v) -> { + if (v == null) { + data.m_propertiesMap.remove(k); + } else { + data.m_propertiesMap.put(k, v); + } + }); + data.refreshProperties(); + } + } + + @Override + public void remove(String name) { + EntryData data = m_entries.remove(name); + if (data != null) { + data.close(); + } + DataLogSendableTable table = m_tables.remove(name); + if (table != null) { + table.close(); + } + } + + @Override + public void addPeriodic(Runnable runnable) { + // TODO + } + + /** + * Return whether this sendable has been published. + * + * @return True if it has been published, false if not. + */ + @Override + public boolean isPublished() { + return true; + } + + /** Update the published values by calling the getters for all properties. */ + @Override + public void update() { + for (EntryData data : m_entries.values()) { + Consumer consumer = data.getPolledUpdate(); + if (consumer != null && data.m_entry != null) { + consumer.accept(data); + } + } + for (DataLogSendableTable table : m_tables.values()) { + table.update(); + } + } + + /** Erases all publishers and subscribers. */ + @Override + public void clear() { + for (EntryData data : m_entries.values()) { + data.close(); + } + m_entries.clear(); + for (DataLogSendableTable table : m_tables.values()) { + table.close(); + } + m_tables.clear(); + } + + /** + * Returns whether there is a data schema already registered with the given name that this + * instance has published. This does NOT perform a check as to whether the schema has already been + * published by another node on the network. + * + * @param name Name (the string passed as the data type for topics using this schema) + * @return True if schema already registered + */ + @Override + public boolean hasSchema(String name) { + return m_log.hasSchema(name); + } + + /** + * Registers a data schema. Data schemas provide information for how a certain data type string + * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. + * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas + * are published just like normal topics, with the name being generated from the provided name: + * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. + * + * @param name Name (the string passed as the data type for topics using this schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + @Override + public void addSchema(String name, String type, byte[] schema) { + m_log.addSchema(name, type, schema); + } + + /** + * Registers a data schema. Data schemas provide information for how a certain data type string + * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. + * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas + * are published just like normal topics, with the name being generated from the provided name: + * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. + * + * @param name Name (the string passed as the data type for topics using this schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + @Override + public void addSchema(String name, String type, String schema) { + m_log.addSchema(name, type, schema); + } + + /** + * Registers a protobuf schema. Duplicate calls to this function with the same name are silently + * ignored. + * + * @param proto protobuf serialization object + */ + @Override + public void addSchema(Protobuf proto) { + m_log.addSchema(proto); + } + + /** + * Registers a struct schema. Duplicate calls to this function with the same name are silently + * ignored. + * + * @param struct struct serialization object + */ + @Override + public void addSchema(Struct struct) { + m_log.addSchema(struct); + } + + /** + * Return whether close() has been called on this sendable table. + * + * @return True if closed, false otherwise. + */ + @Override + public boolean isClosed() { + return m_closed; + } + + @Override + public void close() { + clear(); + if (m_closeSendable != null) { + m_closeSendable.accept(this); + m_closeSendable = null; + } + m_closed = true; + } +} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable2/Sendable.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/Sendable.java new file mode 100644 index 00000000000..dfcacd8a4fc --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/Sendable.java @@ -0,0 +1,51 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.sendable2; + +/** + * Interface for sendable serialization. + * + *

Idiomatically, classes that support sendable serialization should provide a static final + * member named "sendable" that provides an instance of an implementation of this interface. + * + * @param object type + */ +public interface Sendable { + /** + * Gets the Class object for the stored value. + * + * @return Class + */ + Class getTypeClass(); + + /** + * Gets the dashboard type string. + * + * @return dashboard type string + */ + String getTypeString(); + + /** + * Returns true if the object instance has been closed (e.g. is no longer valid). + * + * @param obj object + */ + boolean isClosed(T obj); + + /** + * Sets up a sendable for a particular object instance and table. + * + * @param obj object + * @param table sendable table + */ + void initSendable(T obj, SendableTable table); + + /** + * Closes a sendable for a particular object instance and table. + * + * @param obj object + */ + default void closeSendable(T obj, SendableTable table) {} +} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableOption.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableOption.java new file mode 100644 index 00000000000..ac18072f553 --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableOption.java @@ -0,0 +1,177 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.sendable2; + +/** Sendable publish/subscribe option. */ +public class SendableOption { + /** The option kind. */ + public enum Kind { + kPeriodic, + kTypeString, + kSendAll, + kPollStorage, + kKeepDuplicates, + kDisableRemote, + kDisableLocal + } + + SendableOption(Kind kind, boolean value) { + m_kind = kind; + m_bValue = value; + m_iValue = 0; + m_dValue = 0; + m_sValue = null; + } + + SendableOption(Kind kind, int value) { + m_kind = kind; + m_bValue = false; + m_iValue = value; + m_dValue = 0; + m_sValue = null; + } + + SendableOption(Kind kind, double value) { + m_kind = kind; + m_bValue = false; + m_iValue = 0; + m_dValue = value; + m_sValue = null; + } + + SendableOption(Kind kind, String value) { + m_kind = kind; + m_bValue = false; + m_iValue = 0; + m_dValue = 0; + m_sValue = value; + } + + /** + * Type string. Default is used for the data type if this is empty. Not used for raw values (pass + * the type string directly to the functions). + */ + public static SendableOption typeString(String typeString) { + return new SendableOption(Kind.kTypeString, typeString); + } + + /** + * How frequently changes will be sent over the network. NetworkTables may send more frequently + * than this (e.g. use a combined minimum period for all values) or apply a restricted range to + * this value. The default if unspecified is 100 ms. + * + * @param period time between updates, in seconds + * @return option + */ + public static SendableOption periodic(double period) { + return new SendableOption(Kind.kPeriodic, period); + } + + /** + * If enabled, sends all value changes over the network. This option defaults to disabled. + * + * @param enabled True to enable, false to disable + * @return option + */ + public static SendableOption sendAll(boolean enabled) { + return new SendableOption(Kind.kSendAll, enabled); + } + + /** + * If enabled, preserves duplicate value changes (rather than ignoring them). This option defaults + * to disabled. + * + * @param enabled True to enable, false to disable + * @return option + */ + public static SendableOption keepDuplicates(boolean enabled) { + return new SendableOption(Kind.kKeepDuplicates, enabled); + } + + /** + * Polling storage for subscription. Specifies the maximum number of updates NetworkTables should + * store between calls to the subscriber's readQueue() function. Defaults to 1 if sendAll is + * false, 20 if sendAll is true. + * + * @param depth number of entries to save for polling. + * @return option + */ + public static SendableOption pollStorage(int depth) { + return new SendableOption(Kind.kPollStorage, depth); + } + + /** + * For subscriptions, specify whether remote value updates should not be queued for readQueue(). + * See also disableLocal(). Defaults to false (remote value updates are queued). + * + * @param disabled True to disable, false to enable + * @return option + */ + public static SendableOption disableRemote(boolean disabled) { + return new SendableOption(Kind.kDisableRemote, disabled); + } + + /** + * For subscriptions, specify whether local value updates should not be queued for readQueue(). + * See also disableRemote(). Defaults to false (local value updates are queued). + * + * @param disabled True to disable, false to enable + * @return option + */ + public static SendableOption disableLocal(boolean disabled) { + return new SendableOption(Kind.kDisableLocal, disabled); + } + + /** + * Gets the kind of option. + * + * @return option kind + */ + public Kind getKind() { + return m_kind; + } + + /** + * Gets the stored boolean value. Value is unspecified for non-boolean kinds. + * + * @return boolean value + */ + public boolean getBooleanValue() { + return m_bValue; + } + + /** + * Gets the stored integer value. Value is unspecified for non-integer kinds. + * + * @return integer value + */ + public int getIntValue() { + return m_iValue; + } + + /** + * Gets the stored double value. Value is unspecified for non-double kinds. + * + * @return double value + */ + public double getDoubleValue() { + return m_dValue; + } + + /** + * Gets the stored string value. Value is unspecified for non-string kinds. + * + * @return string value + */ + public String getStringValue() { + return m_sValue; + } + + final Kind m_kind; + final boolean m_bValue; + final int m_iValue; + final double m_dValue; + final String m_sValue; +} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableSerializable.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableSerializable.java new file mode 100644 index 00000000000..b630a7f1c4d --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableSerializable.java @@ -0,0 +1,15 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.sendable2; + +import edu.wpi.first.util.WPISerializable; + +/** + * Marker interface to indicate a class is serializable using Sendable serialization. + * + *

While this cannot be enforced by the interface, any class implementing this interface should + * provide a public final static `sendable` member variable. + */ +public interface SendableSerializable extends WPISerializable {} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableSet.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableSet.java new file mode 100644 index 00000000000..adf0ee9b17e --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableSet.java @@ -0,0 +1,47 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.sendable2; + +import java.util.Collections; +import java.util.Set; +import java.util.WeakHashMap; +import java.util.function.Consumer; + +public class SendableSet implements AutoCloseable { + private final Set m_tables = + Collections.newSetFromMap(new WeakHashMap()); + + @Override + @SuppressWarnings("PMD.AvoidCatchingGenericException") + public void close() { + for (SendableTable table : m_tables) { + try { + table.close(); + } catch (Exception e) { + // ignored + } + } + } + + public void forEach(Consumer action) { + for (SendableTable table : m_tables) { + if (!table.isClosed()) { + action.accept(table); + } + } + } + + public Iterable getAll() { + return m_tables; + } + + public void add(SendableTable table) { + m_tables.add(table); + } + + public void remove(SendableTable table) { + m_tables.remove(table); + } +} diff --git a/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableTable.java b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableTable.java new file mode 100644 index 00000000000..b9698dd9c26 --- /dev/null +++ b/wpiutil/src/main/java/edu/wpi/first/util/sendable2/SendableTable.java @@ -0,0 +1,417 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +package edu.wpi.first.util.sendable2; + +import edu.wpi.first.util.function.BooleanConsumer; +import edu.wpi.first.util.function.FloatConsumer; +import edu.wpi.first.util.function.FloatSupplier; +import edu.wpi.first.util.protobuf.Protobuf; +import edu.wpi.first.util.protobuf.ProtobufBuffer; +import edu.wpi.first.util.struct.Struct; +import edu.wpi.first.util.struct.StructBuffer; +import java.io.IOException; +import java.nio.ByteBuffer; +import java.util.Map; +import java.util.function.BooleanSupplier; +import java.util.function.Consumer; +import java.util.function.DoubleConsumer; +import java.util.function.DoubleSupplier; +import java.util.function.LongConsumer; +import java.util.function.LongSupplier; +import java.util.function.Supplier; + +/** Helper class for building Sendable dashboard representations. */ +public interface SendableTable extends AutoCloseable { + boolean getBoolean(String name, boolean defaultValue); + + long getInteger(String name, long defaultValue); + + float getFloat(String name, float defaultValue); + + double getDouble(String name, double defaultValue); + + String getString(String name, String defaultValue); + + boolean[] getBooleanArray(String name, boolean[] defaultValue); + + long[] getIntegerArray(String name, long[] defaultValue); + + float[] getFloatArray(String name, float[] defaultValue); + + double[] getDoubleArray(String name, double[] defaultValue); + + String[] getStringArray(String name, String[] defaultValue); + + byte[] getRaw(String name, String typeString, byte[] defaultValue); + + default T getStruct(String name, Struct struct, T defaultValue) { + byte[] data = getRaw(name, struct.getTypeString(), null); + if (data == null) { + return defaultValue; + } + // this is inefficient; implementations should cache StructBuffer + StructBuffer buf = StructBuffer.create(struct); + return buf.read(data); + } + + default boolean getStructInto(String name, T out, Struct struct) { + byte[] data = getRaw(name, struct.getTypeString(), null); + if (data == null) { + return false; + } + // this is inefficient; implementations should cache StructBuffer + StructBuffer buf = StructBuffer.create(struct); + buf.readInto(out, data); + return true; + } + + default T getProtobuf(String name, Protobuf proto, T defaultValue) { + byte[] data = getRaw(name, proto.getTypeString(), null); + if (data == null) { + return defaultValue; + } + // this is inefficient; implementations should cache ProtobufBuffer + ProtobufBuffer buf = ProtobufBuffer.create(proto); + try { + return buf.read(data); + } catch (IOException e) { + return defaultValue; + } + } + + default boolean getProtobufInto(String name, T out, Protobuf proto) { + byte[] data = getRaw(name, proto.getTypeString(), null); + if (data == null) { + return false; + } + // this is inefficient; implementations should cache ProtobufBuffer + ProtobufBuffer buf = ProtobufBuffer.create(proto); + try { + buf.readInto(out, data); + } catch (IOException e) { + return false; + } + return true; + } + + void setBoolean(String name, boolean value); + + void setInteger(String name, long value); + + void setFloat(String name, float value); + + void setDouble(String name, double value); + + void setString(String name, String value); + + void setBooleanArray(String name, boolean[] value); + + void setIntegerArray(String name, long[] value); + + void setFloatArray(String name, float[] value); + + void setDoubleArray(String name, double[] value); + + void setStringArray(String name, String[] value); + + void setRaw(String name, String typeString, byte[] value, int start, int len); + + void setRaw(String name, String typeString, ByteBuffer value, int start, int len); + + default void setStruct(String name, T value, Struct struct) { + addSchema(struct); + // this is inefficient; implementations should cache StructBuffer + StructBuffer buf = StructBuffer.create(struct); + ByteBuffer bb = buf.write(value); + setRaw(name, struct.getTypeString(), bb, 0, bb.position()); + } + + default void setProtobuf(String name, T value, Protobuf proto) { + addSchema(proto); + // this is inefficient; implementations should cache ProtobufBuffer + ProtobufBuffer buf = ProtobufBuffer.create(proto); + try { + ByteBuffer bb = buf.write(value); + setRaw(name, proto.getTypeString(), bb, 0, bb.position()); + } catch (IOException e) { + // ignore + } + } + + void publishBoolean(String name, BooleanSupplier supplier); + + void publishInteger(String name, LongSupplier supplier); + + void publishFloat(String name, FloatSupplier supplier); + + void publishDouble(String name, DoubleSupplier supplier); + + void publishString(String name, Supplier supplier); + + void publishBooleanArray(String name, Supplier supplier); + + void publishIntegerArray(String name, Supplier supplier); + + void publishFloatArray(String name, Supplier supplier); + + void publishDoubleArray(String name, Supplier supplier); + + void publishStringArray(String name, Supplier supplier); + + void publishRawBytes(String name, String typeString, Supplier supplier); + + void publishRawBuffer(String name, String typeString, Supplier supplier); + + default void publishStruct(String name, Struct struct, Supplier supplier) { + addSchema(struct); + final StructBuffer buf = StructBuffer.create(struct); + publishRawBuffer( + name, + struct.getTypeString(), + () -> { + return buf.write(supplier.get()); + }); + } + + default void publishProtobuf(String name, Protobuf proto, Supplier supplier) { + addSchema(proto); + final ProtobufBuffer buf = ProtobufBuffer.create(proto); + publishRawBuffer( + name, + proto.getTypeString(), + () -> { + try { + return buf.write(supplier.get()); + } catch (IOException e) { + return null; // ignore + } + }); + } + + BooleanConsumer addBooleanPublisher(String name); + + LongConsumer addIntegerPublisher(String name); + + FloatConsumer addFloatPublisher(String name); + + DoubleConsumer addDoublePublisher(String name); + + Consumer addStringPublisher(String name); + + Consumer addBooleanArrayPublisher(String name); + + Consumer addIntegerArrayPublisher(String name); + + Consumer addFloatArrayPublisher(String name); + + Consumer addDoubleArrayPublisher(String name); + + Consumer addStringArrayPublisher(String name); + + Consumer addRawBytesPublisher(String name, String typeString); + + Consumer addRawBufferPublisher(String name, String typeString); + + default Consumer addStructPublisher(String name, Struct struct) { + addSchema(struct); + final StructBuffer buf = StructBuffer.create(struct); + final Consumer consumer = addRawBufferPublisher(name, struct.getTypeString()); + return value -> { + consumer.accept(buf.write(value)); + }; + } + + default Consumer addProtobufPublisher(String name, Protobuf proto) { + addSchema(proto); + final ProtobufBuffer buf = ProtobufBuffer.create(proto); + final Consumer consumer = addRawBufferPublisher(name, proto.getTypeString()); + return value -> { + try { + consumer.accept(buf.write(value)); + } catch (IOException e) { + // ignore + } + }; + } + + void subscribeBoolean(String name, BooleanConsumer consumer); + + void subscribeInteger(String name, LongConsumer consumer); + + void subscribeFloat(String name, FloatConsumer consumer); + + void subscribeDouble(String name, DoubleConsumer consumer); + + void subscribeString(String name, Consumer consumer); + + void subscribeBooleanArray(String name, Consumer consumer); + + void subscribeIntegerArray(String name, Consumer consumer); + + void subscribeFloatArray(String name, Consumer consumer); + + void subscribeDoubleArray(String name, Consumer consumer); + + void subscribeStringArray(String name, Consumer consumer); + + void subscribeRawBytes(String name, String typeString, Consumer consumer); + + default void subscribeStruct(String name, Struct struct, Consumer consumer) { + addSchema(struct); + final StructBuffer buf = StructBuffer.create(struct); + subscribeRawBytes( + name, + struct.getTypeString(), + data -> { + consumer.accept(buf.read(data)); + }); + } + + default void subscribeProtobuf(String name, Protobuf proto, Consumer consumer) { + addSchema(proto); + final ProtobufBuffer buf = ProtobufBuffer.create(proto); + subscribeRawBytes( + name, + proto.getTypeString(), + data -> { + try { + consumer.accept(buf.read(data)); + } catch (IOException e) { + // ignore + } + }); + } + + SendableTable addSendable(String name, T obj, Sendable sendable); + + SendableTable getChild(String name); + + void setPublishOptions(String name, SendableOption... options); + + void setSubscribeOptions(String name, SendableOption... options); + + /** + * Gets the current value of a property (as a JSON string). + * + * @param name name + * @param propName property name + * @return JSON string; "null" if the property does not exist. + */ + String getProperty(String name, String propName); + + /** + * Sets a property value. + * + * @param name name + * @param propName property name + * @param value property value (JSON string) + * @throws IllegalArgumentException if properties is not parseable as JSON + */ + void setProperty(String name, String propName, String value); + + /** + * Deletes a property. Has no effect if the property does not exist. + * + * @param name name + * @param propName property name + */ + void deleteProperty(String name, String propName); + + /** + * Gets all topic properties as a JSON object string. Each key in the object is the property name, + * and the corresponding value is the property value. + * + * @param name name + * @return JSON string + */ + String getProperties(String name); + + /** + * Updates multiple topic properties. Each key in the passed-in object is the name of the property + * to add/update, and the corresponding value is the property value to set for that property. Null + * values result in deletion of the corresponding property. + * + * @param name name + * @param properties map of keys/JSON values to add/update/delete + * @throws IllegalArgumentException if a properties value is not a JSON object + */ + void setProperties(String name, Map properties); + + void remove(String name); + + void addPeriodic(Runnable runnable); + + /** + * Return whether this sendable has been published. + * + * @return True if it has been published, false if not. + */ + boolean isPublished(); + + /** Update the published values by calling the getters for all properties. */ + void update(); + + /** Erases all publishers and subscribers. */ + void clear(); + + /** + * Returns whether there is a data schema already registered with the given name that this + * instance has published. This does NOT perform a check as to whether the schema has already been + * published by another node on the network. + * + * @param typeString Name (the string passed as the data type for topics using this schema) + * @return True if schema already registered + */ + boolean hasSchema(String typeString); + + /** + * Registers a data schema. Data schemas provide information for how a certain data type string + * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. + * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas + * are published just like normal topics, with the name being generated from the provided name: + * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. + * + * @param typeString Name (the string passed as the data type for topics using this schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + void addSchema(String typeString, String type, byte[] schema); + + /** + * Registers a data schema. Data schemas provide information for how a certain data type string + * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. + * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas + * are published just like normal topics, with the name being generated from the provided name: + * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. + * + * @param typeString Name (the string passed as the data type for topics using this schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + void addSchema(String typeString, String type, String schema); + + /** + * Registers a protobuf schema. Duplicate calls to this function with the same name are silently + * ignored. + * + * @param proto protobuf serialization object + */ + void addSchema(Protobuf proto); + + /** + * Registers a struct schema. Duplicate calls to this function with the same name are silently + * ignored. + * + * @param struct struct serialization object + */ + void addSchema(Struct struct); + + /** + * Return whether close() has been called on this sendable table. + * + * @return True if closed, false otherwise. + */ + boolean isClosed(); +} diff --git a/wpiutil/src/main/native/cpp/DataLogSendableTableBackend.cpp b/wpiutil/src/main/native/cpp/DataLogSendableTableBackend.cpp new file mode 100644 index 00000000000..bfd2660426e --- /dev/null +++ b/wpiutil/src/main/native/cpp/DataLogSendableTableBackend.cpp @@ -0,0 +1,561 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/DataLogSendableTableBackend.h" + +#include + +#include "wpi/json.h" +#include "wpi/sendable2/SendableTable.h" +#include "wpi/SmallString.h" + +using namespace wpi::log; + +struct DataLogSendableTableBackend::EntryData + : public std::enable_shared_from_this { + EntryData(DataLog& log, std::string_view path) : m_log{log}, m_path{path} {} + + template + LogEntryType* Init() { + std::scoped_lock lock{m_mutex}; + if (m_entry.index() == 0) { + m_entry = LogEntryType{m_log, m_path, m_propertiesStr}; + } + return std::get_if(&m_entry); + } + + RawLogEntry* InitRaw(std::string_view typeString) { + std::scoped_lock lock{m_mutex}; + if (m_entry.index() == 0) { + m_entry = RawLogEntry{m_log, m_path, m_propertiesStr, typeString}; + m_typeString = typeString; + } else if (m_typeString != typeString) { + return nullptr; + } + return std::get_if(&m_entry); + } + + template + void Set(T value) { + if (auto entry = std::get_if(&m_entry)) { + if (m_appendAll) { + entry->Append(value); + } else { + entry->Update(value); + } + } + } + + template + void InitAndSet(T value) { + if (Init()) { + Set(value); + } + } + + void InitAndSetRaw(std::string_view typeString, + std::span value) { + if (InitRaw(typeString)) { + Set(value); + } + } + + template + void InitAndPublish(std::function supplier) { + if (Init()) { + SetPolledUpdate([supplier = std::move(supplier)](auto& entry) { + entry.template Set(supplier()); + }); + } + } + + void InitAndPublishRaw(std::string_view typeString, + std::function()> supplier) { + if (InitRaw(typeString)) { + SetPolledUpdate([supplier = std::move(supplier)](auto& entry) { + entry.template Set(supplier()); + }); + } + } + + void InitAndPublishRawSmall( + std::string_view typeString, + std::function(wpi::SmallVectorImpl&)> + supplier) { + if (InitRaw(typeString)) { + SetPolledUpdate([supplier = std::move(supplier)](auto& entry) { + wpi::SmallVector buf; + entry.template Set(supplier(buf)); + }); + } + } + + template + std::function AddPublisher() { + auto entry = Init(); + if (!entry) { + return [](auto) {}; + } + return [weak = weak_from_this()](auto value) { + if (auto self = weak.lock()) { + self->Set(value); + } + }; + } + + std::function)> AddPublisherRaw( + std::string_view typeString) { + auto entry = InitRaw(typeString); + if (!entry) { + return [](auto) {}; + } + return [weak = weak_from_this()](auto value) { + if (auto self = weak.lock()) { + self->Set(value); + } + }; + } + + template + LogEntryType* Get() { + std::scoped_lock lock{m_mutex}; + if (m_entry.index() == 0) { + return nullptr; + } + return std::get_if(&m_entry); + } + + void SetPolledUpdate(std::function function) { + std::scoped_lock lock{m_mutex}; + m_polledUpdate = std::move(function); + } + + void RefreshProperties(); + + std::variant + m_entry; + DataLog& m_log; + wpi::mutex m_mutex; + std::string m_path; + std::string m_typeString; + wpi::json m_properties; + std::string m_propertiesStr; + std::function m_polledUpdate; + bool m_appendAll{false}; +}; + +void DataLogSendableTableBackend::EntryData::RefreshProperties() { + m_propertiesStr.clear(); + wpi::raw_string_ostream os{m_propertiesStr}; + m_properties.dump(os); + if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } else if (auto entry = std::get_if(&m_entry)) { + entry->SetMetadata(m_propertiesStr); + } +} + +DataLogSendableTableBackend::DataLogSendableTableBackend(DataLog& log, + std::string_view path) + : m_log{log}, m_path{path} {} + +DataLogSendableTableBackend::~DataLogSendableTableBackend() = default; + +bool DataLogSendableTableBackend::GetBoolean(std::string_view name, + bool defaultValue) { + std::scoped_lock lock{m_mutex}; + auto it = m_entries.find(name); + if (it != m_entries.end()) { + if (auto entry = it->second->Get()) { + if (auto val = entry->GetLastValue()) { + return *val; + } + } + } + return defaultValue; +} + +int64_t DataLogSendableTableBackend::GetInteger(std::string_view name, + int64_t defaultValue) { + std::scoped_lock lock{m_mutex}; + auto it = m_entries.find(name); + if (it != m_entries.end()) { + if (auto entry = it->second->Get()) { + if (auto val = entry->GetLastValue()) { + return *val; + } + } + } + return defaultValue; +} + +float DataLogSendableTableBackend::GetFloat(std::string_view name, + float defaultValue) { + std::scoped_lock lock{m_mutex}; + auto it = m_entries.find(name); + if (it != m_entries.end()) { + if (auto entry = it->second->Get()) { + if (auto val = entry->GetLastValue()) { + return *val; + } + } + } + return defaultValue; +} + +double DataLogSendableTableBackend::GetDouble(std::string_view name, + double defaultValue) { + std::scoped_lock lock{m_mutex}; + auto it = m_entries.find(name); + if (it != m_entries.end()) { + if (auto entry = it->second->Get()) { + if (auto val = entry->GetLastValue()) { + return *val; + } + } + } + return defaultValue; +} + +std::string DataLogSendableTableBackend::GetString( + std::string_view name, std::string_view defaultValue) { + std::scoped_lock lock{m_mutex}; + auto it = m_entries.find(name); + if (it != m_entries.end()) { + if (auto entry = it->second->Get()) { + if (auto val = entry->GetLastValue()) { + return *val; + } + } + } + return {defaultValue.data(), defaultValue.size()}; +} + +std::vector DataLogSendableTableBackend::GetRaw( + std::string_view name, std::string_view typeString, + std::span defaultValue) { + std::scoped_lock lock{m_mutex}; + auto it = m_entries.find(name); + if (it != m_entries.end()) { + if (auto entry = it->second->Get()) { + if (auto val = entry->GetLastValue()) { + return *val; + } + } + } + return {defaultValue.begin(), defaultValue.end()}; +} + +void DataLogSendableTableBackend::SetBoolean(std::string_view name, + bool value) { + GetOrNew(name).InitAndSet(value); +} + +void DataLogSendableTableBackend::SetInteger(std::string_view name, + int64_t value) { + GetOrNew(name).InitAndSet(value); +} + +void DataLogSendableTableBackend::SetFloat(std::string_view name, float value) { + GetOrNew(name).InitAndSet(value); +} + +void DataLogSendableTableBackend::SetDouble(std::string_view name, + double value) { + GetOrNew(name).InitAndSet(value); +} + +void DataLogSendableTableBackend::SetString(std::string_view name, + std::string_view value) { + GetOrNew(name).InitAndSet(value); +} + +void DataLogSendableTableBackend::SetRaw(std::string_view name, + std::string_view typeString, + std::span value) { + GetOrNew(name).InitAndSetRaw(typeString, value); +} + +void DataLogSendableTableBackend::PublishBoolean( + std::string_view name, std::function supplier) { + GetOrNew(name).InitAndPublish(std::move(supplier)); +} + +void DataLogSendableTableBackend::PublishInteger( + std::string_view name, std::function supplier) { + GetOrNew(name).InitAndPublish(std::move(supplier)); +} + +void DataLogSendableTableBackend::PublishFloat( + std::string_view name, std::function supplier) { + GetOrNew(name).InitAndPublish(std::move(supplier)); +} + +void DataLogSendableTableBackend::PublishDouble( + std::string_view name, std::function supplier) { + GetOrNew(name).InitAndPublish(std::move(supplier)); +} + +void DataLogSendableTableBackend::PublishString( + std::string_view name, std::function supplier) { + GetOrNew(name).InitAndPublish(std::move(supplier)); +} + +void DataLogSendableTableBackend::PublishRaw( + std::string_view name, std::string_view typeString, + std::function()> supplier) { + GetOrNew(name).InitAndPublishRaw(typeString, std::move(supplier)); +} + +void DataLogSendableTableBackend::PublishRawSmall( + std::string_view name, std::string_view typeString, + std::function(wpi::SmallVectorImpl& buf)> + supplier) { + GetOrNew(name).InitAndPublishRawSmall(typeString, std::move(supplier)); +} + +[[nodiscard]] +std::function DataLogSendableTableBackend::AddBooleanPublisher( + std::string_view name) { + return GetOrNew(name).AddPublisher(); +} + +[[nodiscard]] +std::function DataLogSendableTableBackend::AddIntegerPublisher( + std::string_view name) { + return GetOrNew(name).AddPublisher(); +} + +[[nodiscard]] +std::function DataLogSendableTableBackend::AddFloatPublisher( + std::string_view name) { + return GetOrNew(name).AddPublisher(); +} + +[[nodiscard]] +std::function DataLogSendableTableBackend::AddDoublePublisher( + std::string_view name) { + return GetOrNew(name).AddPublisher(); +} + +[[nodiscard]] +std::function +DataLogSendableTableBackend::AddStringPublisher(std::string_view name) { + return GetOrNew(name).AddPublisher(); +} + +[[nodiscard]] +std::function)> +DataLogSendableTableBackend::AddRawPublisher(std::string_view name, + std::string_view typeString) { + return GetOrNew(name).AddPublisherRaw(typeString); +} + +void DataLogSendableTableBackend::SubscribeBoolean( + std::string_view name, std::function consumer) {} + +void DataLogSendableTableBackend::SubscribeInteger( + std::string_view name, std::function consumer) {} + +void DataLogSendableTableBackend::SubscribeFloat( + std::string_view name, std::function consumer) {} + +void DataLogSendableTableBackend::SubscribeDouble( + std::string_view name, std::function consumer) {} + +void DataLogSendableTableBackend::SubscribeString( + std::string_view name, std::function consumer) {} + +void DataLogSendableTableBackend::SubscribeRaw( + std::string_view name, std::string_view typeString, + std::function)> consumer) {} + +std::shared_ptr +DataLogSendableTableBackend::CreateSendable( + std::string_view name, std::unique_ptr sendable) { + auto child = GetChildInternal(name); + std::scoped_lock lock{child->m_mutex}; + if (!child->m_sendable) { + wpi2::SendableTable table{child}; + sendable->Init(table); + child->m_sendable = std::move(sendable); + } + return child; +} + +std::shared_ptr +DataLogSendableTableBackend::GetChild(std::string_view name) { + return GetChildInternal(name); +} + +void DataLogSendableTableBackend::SetPublishOptions( + std::string_view name, const wpi2::SendableOptions& options) { + auto& entry = GetOrNew(name); + if (options.keepDuplicates) { + entry.m_appendAll = true; + } +} + +void DataLogSendableTableBackend::SetSubscribeOptions( + std::string_view name, const wpi2::SendableOptions& options) {} + +wpi::json DataLogSendableTableBackend::GetProperty( + std::string_view name, std::string_view propName) const { + std::scoped_lock lock{m_mutex}; + auto entryIt = m_entries.find(name); + if (entryIt == m_entries.end()) { + return {}; + } + std::scoped_lock lock2{entryIt->second->m_mutex}; + auto valueIt = entryIt->second->m_properties.find(propName); + if (valueIt == entryIt->second->m_properties.end()) { + return {}; + } + return valueIt.value(); +} + +void DataLogSendableTableBackend::SetProperty(std::string_view name, + std::string_view propName, + const wpi::json& value) { + auto& data = GetOrNew(name); + std::scoped_lock lock{data.m_mutex}; + data.m_properties[propName] = value; + data.RefreshProperties(); +} + +void DataLogSendableTableBackend::DeleteProperty(std::string_view name, + std::string_view propName) { + auto& data = GetOrNew(name); + std::scoped_lock lock{data.m_mutex}; + data.m_properties.erase(propName); + data.RefreshProperties(); +} + +wpi::json DataLogSendableTableBackend::GetProperties( + std::string_view name) const { + std::scoped_lock lock{m_mutex}; + auto entryIt = m_entries.find(name); + if (entryIt == m_entries.end()) { + return wpi::json::object(); + } + std::scoped_lock lock2{entryIt->second->m_mutex}; + return entryIt->second->m_properties; +} + +bool DataLogSendableTableBackend::SetProperties(std::string_view name, + const wpi::json& properties) { + if (!properties.is_object()) { + return false; + } + if (properties.empty()) { + return true; + } + auto& data = GetOrNew(name); + std::scoped_lock lock{data.m_mutex}; + for (auto&& [key, value] : properties.items()) { + if (value.is_null()) { + data.m_properties.erase(key); + } else { + data.m_properties[key] = value; + } + } + data.RefreshProperties(); + return true; +} + +void DataLogSendableTableBackend::Remove(std::string_view name) { + std::scoped_lock lock{m_mutex}; + m_entries.erase(name); + m_tables.erase(name); +} + +bool DataLogSendableTableBackend::IsPublished() const { + return true; +} + +void DataLogSendableTableBackend::Update() { + std::scoped_lock lock{m_mutex}; + for (auto&& data : m_entries) { + std::scoped_lock lock{data.second->m_mutex}; + auto& consumer = data.second->m_polledUpdate; + if (consumer && + !std::holds_alternative(data.second->m_entry)) { + consumer(*data.second); + } + } + for (auto&& table : m_tables) { + table.second->Update(); + } +} + +void DataLogSendableTableBackend::Clear() { + std::scoped_lock lock{m_mutex}; + m_entries.clear(); + m_tables.clear(); +} + +bool DataLogSendableTableBackend::HasSchema(std::string_view name) const { + return m_log.HasSchema(name); +} + +void DataLogSendableTableBackend::AddSchema(std::string_view name, + std::string_view type, + std::span schema) { + m_log.AddSchema(name, type, schema); +} + +void DataLogSendableTableBackend::AddSchema(std::string_view name, + std::string_view type, + std::string_view schema) { + m_log.AddSchema(name, type, schema); +} + +DataLogSendableTableBackend::EntryData& DataLogSendableTableBackend::GetOrNew( + std::string_view name) { + std::scoped_lock lock{m_mutex}; + auto& entry = m_entries[name]; + if (!entry) { + wpi::SmallString<128> path{m_path}; + path += '/'; + path += name; + entry = std::make_shared(m_log, path); + } + return *entry; +} + +std::shared_ptr +DataLogSendableTableBackend::GetChildInternal(std::string_view name) { + std::scoped_lock lock{m_mutex}; + auto& child = m_tables[name]; + if (!child) { + wpi::SmallString<128> path{m_path}; + path += '/'; + path += name; + child = std::make_shared(m_log, path); + } + return child; +} diff --git a/wpiutil/src/main/native/cpp/sendable2/SendableSet.cpp b/wpiutil/src/main/native/cpp/sendable2/SendableSet.cpp new file mode 100644 index 00000000000..13d6386250f --- /dev/null +++ b/wpiutil/src/main/native/cpp/sendable2/SendableSet.cpp @@ -0,0 +1,29 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/sendable2/SendableSet.h" + +using namespace wpi2; + +std::vector SendableSet::GetAll() const { + std::vector tables; + tables.reserve(m_tables.size()); + ForEach([&](auto&& backend) { tables.emplace_back(backend); }); + return tables; +} + +void SendableSet::Add(const SendableTable& table) { + auto backend = table.GetBackend(); + for (auto&& backendWeak : m_tables) { + if (backendWeak.lock() == backend) { + return; + } + } + m_tables.emplace_back(table.GetWeak()); +} + +void SendableSet::Remove(const SendableTable& table) { + auto backend = table.GetBackend(); + std::erase_if(m_tables, [&](const auto& v) { return v.lock() == backend; }); +} diff --git a/wpiutil/src/main/native/cpp/sendable2/SendableTable.cpp b/wpiutil/src/main/native/cpp/sendable2/SendableTable.cpp new file mode 100644 index 00000000000..6d2d4ecd258 --- /dev/null +++ b/wpiutil/src/main/native/cpp/sendable2/SendableTable.cpp @@ -0,0 +1,208 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#include "wpi/sendable2/SendableTable.h" + +#include "wpi/json.h" +#include "wpi/sendable2/SendableTableBackend.h" + +using namespace wpi2; + +void SendableTable::SetBoolean(std::string_view name, bool value) { + m_backend->SetBoolean(name, value); +} + +void SendableTable::SetInteger(std::string_view name, int64_t value) { + m_backend->SetInteger(name, value); +} + +void SendableTable::SetFloat(std::string_view name, float value) { + m_backend->SetFloat(name, value); +} + +void SendableTable::SetDouble(std::string_view name, double value) { + m_backend->SetDouble(name, value); +} + +void SendableTable::SetString(std::string_view name, std::string_view value) { + m_backend->SetString(name, value); +} + +void SendableTable::SetRaw(std::string_view name, std::string_view typeString, + std::span value) { + m_backend->SetRaw(name, typeString, value); +} + +void SendableTable::PublishBoolean(std::string_view name, + std::function supplier) { + m_backend->PublishBoolean(name, std::move(supplier)); +} + +void SendableTable::PublishInteger(std::string_view name, + std::function supplier) { + m_backend->PublishInteger(name, std::move(supplier)); +} + +void SendableTable::PublishFloat(std::string_view name, + std::function supplier) { + m_backend->PublishFloat(name, std::move(supplier)); +} + +void SendableTable::PublishDouble(std::string_view name, + std::function supplier) { + m_backend->PublishDouble(name, std::move(supplier)); +} + +void SendableTable::PublishString(std::string_view name, + std::function supplier) { + m_backend->PublishString(name, std::move(supplier)); +} + +void SendableTable::PublishRaw(std::string_view name, + std::string_view typeString, + std::function()> supplier) { + m_backend->PublishRaw(name, typeString, std::move(supplier)); +} + +void SendableTable::PublishRawSmall( + std::string_view name, std::string_view typeString, + std::function(wpi::SmallVectorImpl& buf)> + supplier) { + m_backend->PublishRawSmall(name, typeString, std::move(supplier)); +} + +std::function SendableTable::AddBooleanPublisher( + std::string_view name) { + return m_backend->AddBooleanPublisher(name); +} + +std::function SendableTable::AddIntegerPublisher( + std::string_view name) { + return m_backend->AddIntegerPublisher(name); +} + +void SendableTable::SubscribeBoolean(std::string_view name, + std::function consumer) { + m_backend->SubscribeBoolean(name, std::move(consumer)); +} + +void SendableTable::SubscribeInteger(std::string_view name, + std::function consumer) { + m_backend->SubscribeInteger(name, std::move(consumer)); +} + +std::function SendableTable::AddFloatPublisher( + std::string_view name) { + return m_backend->AddFloatPublisher(name); +} + +std::function SendableTable::AddDoublePublisher( + std::string_view name) { + return m_backend->AddDoublePublisher(name); +} + +std::function SendableTable::AddStringPublisher( + std::string_view name) { + return m_backend->AddStringPublisher(name); +} + +std::function)> SendableTable::AddRawPublisher( + std::string_view name, std::string_view typeString) { + return m_backend->AddRawPublisher(name, typeString); +} + +void SendableTable::SubscribeFloat(std::string_view name, + std::function consumer) { + m_backend->SubscribeFloat(name, std::move(consumer)); +} + +void SendableTable::SubscribeDouble(std::string_view name, + std::function consumer) { + m_backend->SubscribeDouble(name, std::move(consumer)); +} + +void SendableTable::SubscribeString( + std::string_view name, std::function consumer) { + m_backend->SubscribeString(name, std::move(consumer)); +} + +void SendableTable::SubscribeRaw( + std::string_view name, std::string_view typeString, + std::function)> consumer) { + m_backend->SubscribeRaw(name, typeString, std::move(consumer)); +} + +SendableTable SendableTable::GetChild(std::string_view name) { + return SendableTable{m_backend->GetChild(name)}; +} + +void SendableTable::SetPublishOptions(std::string_view name, + const SendableOptions& options) { + m_backend->SetPublishOptions(name, options); +} + +void SendableTable::SetSubscribeOptions(std::string_view name, + const SendableOptions& options) { + m_backend->SetSubscribeOptions(name, options); +} + +wpi::json SendableTable::GetProperty(std::string_view name, + std::string_view propName) const { + return m_backend->GetProperty(name, propName); +} + +void SendableTable::SetProperty(std::string_view name, + std::string_view propName, + const wpi::json& value) { + m_backend->SetProperty(name, propName, value); +} + +void SendableTable::DeleteProperty(std::string_view name, + std::string_view propName) { + m_backend->DeleteProperty(name, propName); +} + +wpi::json SendableTable::GetProperties(std::string_view name) const { + return m_backend->GetProperties(name); +} + +bool SendableTable::SetProperties(std::string_view name, + const wpi::json& properties) { + return m_backend->SetProperties(name, properties); +} + +void SendableTable::Remove(std::string_view name) { + return m_backend->Remove(name); +} + +bool SendableTable::IsPublished() const { + return m_backend->IsPublished(); +} + +void SendableTable::Update() { + m_backend->Update(); +} + +void SendableTable::Clear() { + m_backend->Clear(); +} + +bool SendableTable::HasSchema(std::string_view name) const { + return m_backend->HasSchema(name); +} + +void SendableTable::AddSchema(std::string_view name, std::string_view type, + std::span schema) { + m_backend->AddSchema(name, type, schema); +} + +void SendableTable::AddSchema(std::string_view name, std::string_view type, + std::string_view schema) { + m_backend->AddSchema(name, type, schema); +} + +SendableTable SendableTable::CreateSendable( + std::string_view name, std::unique_ptr sendable) { + return SendableTable{m_backend->CreateSendable(name, std::move(sendable))}; +} diff --git a/wpiutil/src/main/native/include/wpi/DataLog.h b/wpiutil/src/main/native/include/wpi/DataLog.h index 35f0859ec0f..1253b5bc13d 100644 --- a/wpiutil/src/main/native/include/wpi/DataLog.h +++ b/wpiutil/src/main/native/include/wpi/DataLog.h @@ -876,7 +876,7 @@ class StringLogEntry : public DataLogValueEntryImpl { * @param value Value to record * @param timestamp Time stamp (may be 0 to indicate now) */ - void Update(std::string value, int64_t timestamp = 0) { + void Update(std::string_view value, int64_t timestamp = 0) { std::scoped_lock lock{m_mutex}; if (m_lastValue != value) { m_lastValue = value; diff --git a/wpiutil/src/main/native/include/wpi/DataLogSendableTableBackend.h b/wpiutil/src/main/native/include/wpi/DataLogSendableTableBackend.h new file mode 100644 index 00000000000..116987c7c9d --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/DataLogSendableTableBackend.h @@ -0,0 +1,190 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +#include +#include +#include +#include + +#include "wpi/mutex.h" +#include "wpi/sendable2/SendableTableBackend.h" +#include "wpi/DataLog.h" +#include "wpi/StringMap.h" + +namespace wpi::log { + +/** DataLog-backed implementation of SendableTable. */ +class DataLogSendableTableBackend : public wpi2::SendableTableBackend { + public: + /** + * Constructs a DataLog-backed sendable table. + * + * @param log DataLog + * @param path path to sendable table, excluding trailing '/' + */ + DataLogSendableTableBackend(DataLog& log, std::string_view path); + ~DataLogSendableTableBackend(); + + /** + * Gets the DataLog for the table. + * + * @return DataLog + */ + DataLog& GetLog() const { return m_log; } + + bool GetBoolean(std::string_view name, bool defaultValue) override; + + int64_t GetInteger(std::string_view name, int64_t defaultValue) override; + + float GetFloat(std::string_view name, float defaultValue) override; + + double GetDouble(std::string_view name, double defaultValue) override; + + std::string GetString(std::string_view name, + std::string_view defaultValue) override; + + std::vector GetRaw(std::string_view name, + std::string_view typeString, + std::span defaultValue) override; + + void SetBoolean(std::string_view name, bool value) override; + + void SetInteger(std::string_view name, int64_t value) override; + + void SetFloat(std::string_view name, float value) override; + + void SetDouble(std::string_view name, double value) override; + + void SetString(std::string_view name, std::string_view value) override; + + void SetRaw(std::string_view name, std::string_view typeString, + std::span value) override; + + void PublishBoolean(std::string_view name, + std::function supplier) override; + + void PublishInteger(std::string_view name, + std::function supplier) override; + + void PublishFloat(std::string_view name, + std::function supplier) override; + + void PublishDouble(std::string_view name, + std::function supplier) override; + + void PublishString(std::string_view name, + std::function supplier) override; + + void PublishRaw(std::string_view name, std::string_view typeString, + std::function()> supplier) override; + + void PublishRawSmall( + std::string_view name, std::string_view typeString, + std::function(wpi::SmallVectorImpl& buf)> + supplier) override; + + [[nodiscard]] + std::function AddBooleanPublisher(std::string_view name) override; + + [[nodiscard]] + std::function AddIntegerPublisher( + std::string_view name) override; + + [[nodiscard]] + std::function AddFloatPublisher(std::string_view name) override; + + [[nodiscard]] + std::function AddDoublePublisher( + std::string_view name) override; + + [[nodiscard]] + std::function AddStringPublisher( + std::string_view name) override; + + [[nodiscard]] + std::function)> AddRawPublisher( + std::string_view name, std::string_view typeString) override; + + void SubscribeBoolean(std::string_view name, + std::function consumer) override; + + void SubscribeInteger(std::string_view name, + std::function consumer) override; + + void SubscribeFloat(std::string_view name, + std::function consumer) override; + + void SubscribeDouble(std::string_view name, + std::function consumer) override; + + void SubscribeString(std::string_view name, + std::function consumer) override; + + void SubscribeRaw( + std::string_view name, std::string_view typeString, + std::function)> consumer) override; + + std::shared_ptr CreateSendable( + std::string_view name, + std::unique_ptr sendable) override; + + std::shared_ptr GetChild( + std::string_view name) override; + + void SetPublishOptions(std::string_view name, + const wpi2::SendableOptions& options) override; + + void SetSubscribeOptions(std::string_view name, + const wpi2::SendableOptions& options) override; + + wpi::json GetProperty(std::string_view name, + std::string_view propName) const override; + + void SetProperty(std::string_view name, std::string_view propName, + const wpi::json& value) override; + + void DeleteProperty(std::string_view name, + std::string_view propName) override; + + wpi::json GetProperties(std::string_view name) const override; + + bool SetProperties(std::string_view name, + const wpi::json& properties) override; + + void Remove(std::string_view name) override; + + bool IsPublished() const override; + + void Update() override; + + void Clear() override; + + bool HasSchema(std::string_view name) const override; + + void AddSchema(std::string_view name, std::string_view type, + std::span schema) override; + + void AddSchema(std::string_view name, std::string_view type, + std::string_view schema) override; + + private: + struct EntryData; + + DataLog& m_log; + std::string m_path; + mutable wpi::recursive_mutex m_mutex; + wpi::StringMap> m_entries; + wpi::StringMap> m_tables; + std::unique_ptr m_sendable; + + EntryData& GetOrNew(std::string_view name); + std::shared_ptr GetChildInternal( + std::string_view name); +}; + +} // namespace wpi::log diff --git a/wpiutil/src/main/native/include/wpi/MoveTracker.h b/wpiutil/src/main/native/include/wpi/MoveTracker.h new file mode 100644 index 00000000000..0a731058742 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/MoveTracker.h @@ -0,0 +1,134 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include + +#if 0 +#include "wpi/Signal.h" +#endif + +namespace wpi { + +class MoveTrackerBase; + +template +concept MoveTracked = std::derived_from; + +namespace detail { +class MoveTrackerData { + friend class wpi::MoveTrackerBase; + + public: + explicit MoveTrackerData(MoveTrackerBase* ptr) : m_ptr{ptr} {} + +#if 0 + ~MoveTrackerData() { + if (m_ptr != nullptr) { + onMove(nullptr, m_ptr); + } + } +#endif + MoveTrackerData(const MoveTrackerData&) = delete; + MoveTrackerData& operator=(const MoveTrackerData&) = delete; + + MoveTrackerBase* Get() const { return m_ptr; } + +#if 0 + /** + * Signal called on object move or deletion. + * First parameter is the destination (moved to) object pointer, or nullptr + * if the object is being deleted. + * Second parameter is the source (moved from) object pointer. + * + * @note On move, the callback is called from the MoveTrackerBase base class + * move constructor, so the pointer cannot be safely upcasted and used in the + * callback itself because the move has not yet completed for the derived + * class. + */ + wpi::sig::Signal_mt onMove; +#endif + + private: + void Move(MoveTrackerBase* to, MoveTrackerBase* from) { + m_ptr = to; +#if 0 + onMove(to, from); +#endif + } + + std::atomic m_ptr; +}; +} // namespace detail + +/** + * Base class that calls a list of callbacks on object move and deletion. + * The list of callbacks is not copied or changed on object copy. + */ +class MoveTrackerBase { + template + friend class MoveWeakPtr; + + public: + virtual ~MoveTrackerBase() { + if (m_tracker) { + m_tracker->Move(nullptr, this); + } + } + + MoveTrackerBase(const MoveTrackerBase& rhs) noexcept {} + MoveTrackerBase& operator=(const MoveTrackerBase& rhs) noexcept { // NOLINT + return *this; + } + + MoveTrackerBase(MoveTrackerBase&& rhs) : m_tracker{std::move(rhs.m_tracker)} { + if (m_tracker) { + m_tracker->Move(this, &rhs); + } + } + + MoveTrackerBase& operator=(MoveTrackerBase&& rhs) { + if (&rhs != this) { + if (m_tracker) { + m_tracker->Move(nullptr, this); + } + m_tracker = std::move(rhs.m_tracker); + if (m_tracker) { + m_tracker->Move(this, &rhs); + } + } + return *this; + } + + protected: + MoveTrackerBase() + : m_tracker{std::make_shared(this)} {} + + private: + std::shared_ptr m_tracker; +}; + +template +class MoveWeakPtr { + public: + explicit MoveWeakPtr(T* ptr) + : m_data{static_cast(ptr)->m_tracker} {} + + T* Get() const { + if (auto data = m_data.lock()) { + return static_cast(data->Get()); + } else { + return nullptr; + } + } + + private: + std::weak_ptr m_data; +}; + +} // namespace wpi diff --git a/wpiutil/src/main/native/include/wpi/sendable2/Sendable.h b/wpiutil/src/main/native/include/wpi/sendable2/Sendable.h new file mode 100644 index 00000000000..f31478743e4 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/sendable2/Sendable.h @@ -0,0 +1,162 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include + +namespace wpi { +class MoveTrackerBase; +} // namespace wpi + +namespace wpi2 { + +class SendableTable; + +namespace detail { + +template +concept MoveTracked = std::derived_from; + +} // namespace detail + +/** + * Sendable serialization template. Unspecialized class has no members; only + * specializations of this class are useful, and only if they meet the + * SendableSerializable concept. + * + * @tparam T type to serialize/deserialize + * @tparam I optional sendable type info + */ +template +struct Sendable {}; + +/** + * Specifies that a type is capable of sendable serialization and + * deserialization via a raw pointer. This requires the type be derived from + * wpi::MoveTracker so that moves can be tracked. + * + * Implementations must define a template specialization for wpi::Sendable with + * T being the type that is being serialized/deserialized, with the following + * static members (as enforced by this concept): + * - std::string_view GetTypeString(): function that returns the dashboard type + * string + * - void Init(T* obj, SendableTable& table) + * - void Close(T* obj, SendableTable& table) + * + * If possible, the GetTypeString() function should be marked constexpr. + * GetTypeString() may return a type other than std::string_view, as long as the + * return value is convertible to std::string_view. + */ +template +concept SendableSerializableMoveTracked = + detail::MoveTracked && + requires(T* obj, SendableTable& table, const I&... info) { + typename Sendable, + typename std::remove_cvref_t...>; + { + Sendable, + typename std::remove_cvref_t...>::GetTypeString(info...) + } -> std::convertible_to; + Sendable, + typename std::remove_cvref_t...>::Init(obj, table, info...); + Sendable, + typename std::remove_cvref_t...>::Close(obj, table, info...); + }; + +/** + * Specifies that a type is capable of sendable serialization and + * deserialization when wrapped in a std::shared_ptr. This works with any type. + * + * Implementations must define a template specialization for wpi::Sendable with + * T being the type that is being serialized/deserialized, with the following + * static members (as enforced by this concept): + * - std::string_view GetTypeString(): function that returns the dashboard type + * string + * - void Init(std::shared_ptr obj, SendableTable& table) + * - void Close(std::shared_ptr obj, SendableTable& table) + * + * If possible, the GetTypeString() function should be marked constexpr. + * GetTypeString() may return a type other than std::string_view, as long as the + * return value is convertible to std::string_view. + */ +template +concept SendableSerializableSharedPointer = + requires(std::shared_ptr obj, SendableTable& table, const I&... info) { + typename Sendable, + typename std::remove_cvref_t...>; + { + Sendable, + typename std::remove_cvref_t...>::GetTypeString(info...) + } -> std::convertible_to; + Sendable, + typename std::remove_cvref_t...>::Init(obj, table, info...); + Sendable, + typename std::remove_cvref_t...>::Close(obj, table, info...); + }; + +/** + * Specifies that a type is capable of sendable serialization and + * deserialization as either a raw pointer type or via std::shared_ptr. + */ +template +concept SendableSerializable = SendableSerializableMoveTracked || + SendableSerializableSharedPointer; + +/** + * Get the type string for a sendable serializable type + * + * @tparam T serializable type + * @param info optional struct type info + * @return type string + */ +template + requires SendableSerializable +constexpr auto GetSendableTypeString(const I&... info) { + using S = Sendable...>; + return S::GetTypeString(info...); +} + +template + requires SendableSerializableMoveTracked +inline void InitSendable(T* obj, SendableTable& table, const I&... info) { + using S = Sendable...>; + S::Init(obj, table, info...); +} + +template + requires SendableSerializable +inline void InitSendable(std::shared_ptr obj, SendableTable& table, + const I&... info) { + using S = Sendable...>; + if constexpr (SendableSerializableSharedPointer) { + S::Init(std::move(obj), table, info...); + } else if constexpr (SendableSerializableMoveTracked) { + S::Init(obj.get(), table, info...); + } +} + +template + requires SendableSerializableMoveTracked +inline void CloseSendable(T* obj, SendableTable& table, const I&... info) { + using S = Sendable...>; + S::Close(obj, table, info...); +} + +template + requires SendableSerializable +inline void CloseSendable(std::shared_ptr obj, SendableTable& table, + const I&... info) { + using S = Sendable...>; + if constexpr (SendableSerializableSharedPointer) { + S::Close(std::move(obj), table, info...); + } else if constexpr (SendableSerializableMoveTracked) { + S::Close(obj.get(), table, info...); + } +} + +} // namespace wpi2 diff --git a/wpiutil/src/main/native/include/wpi/sendable2/SendableOptions.h b/wpiutil/src/main/native/include/wpi/sendable2/SendableOptions.h new file mode 100644 index 00000000000..a92f784acb7 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/sendable2/SendableOptions.h @@ -0,0 +1,62 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +namespace wpi2 { + +/** Sendable publish/subscribe options. */ +struct SendableOptions { + /** + * Default value of periodic. + */ + static constexpr double kDefaultPeriodic = 0.1; + + /** + * Type string. Default is used for the data type if this is empty. + * Not used for raw values (pass the type string directly to the functions). + */ + std::string_view typeString; + + /** + * Polling storage size for a subscription. Specifies the maximum number of + * updates the backend should store. If zero, defaults to 1 if sendAll is + * false, 20 if sendAll is true. + */ + unsigned int pollStorage = 0; + + /** + * How frequently changes will be sent over the network, in seconds. + * The backend may send more frequently than this (e.g. use a combined + * minimum period for all values) or apply a restricted range to this value. + * The default is 100 ms. + */ + double periodic = kDefaultPeriodic; + + /** + * Send all value changes. + */ + bool sendAll = false; + + /** + * Preserve duplicate value changes (rather than ignoring them). + */ + bool keepDuplicates = false; + + /** + * For subscriptions, if remote value updates should be ignored. See also + * disableLocal. + */ + bool disableRemote = false; + + /** + * For subscriptions, if local value updates should be ignored. See also + * disableRemote. + */ + bool disableLocal = false; +}; + +} // namespace wpi2 diff --git a/wpiutil/src/main/native/include/wpi/sendable2/SendableSet.h b/wpiutil/src/main/native/include/wpi/sendable2/SendableSet.h new file mode 100644 index 00000000000..3b465388a9f --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/sendable2/SendableSet.h @@ -0,0 +1,37 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include + +#include "wpi/sendable2/SendableTable.h" + +namespace wpi2 { + +class SendableSet final { + public: + std::vector GetAll() const; + + template F> + void ForEach(F&& func) const { + for (auto&& backendWeak : m_tables) { + if (auto backend = backendWeak.lock()) { + std::invoke(std::forward(func), SendableTable{std::move(backend)}); + } + } + } + + void Add(const SendableTable& table); + + void Remove(const SendableTable& table); + + private: + std::vector> m_tables; +}; + +} // namespace wpi2 diff --git a/wpiutil/src/main/native/include/wpi/sendable2/SendableTable.h b/wpiutil/src/main/native/include/wpi/sendable2/SendableTable.h new file mode 100644 index 00000000000..7b686aba116 --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/sendable2/SendableTable.h @@ -0,0 +1,318 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include +#include + +#include "wpi/json_fwd.h" +#include "wpi/protobuf/Protobuf.h" +#include "wpi/sendable2/Sendable.h" +#include "wpi/sendable2/SendableOptions.h" +#include "wpi/protobuf/Protobuf.h" +#include "wpi/struct/Struct.h" + +namespace wpi { +template +class SmallVectorImpl; +} // namespace wpi + +namespace wpi2 { + +class SendableTableBackend; +class SendableWrapper; + +class SendableTable { + public: + explicit SendableTable(std::shared_ptr backend) + : m_backend{std::move(backend)} {} + + bool GetBoolean(std::string_view name, bool defaultValue); + + int64_t GetInteger(std::string_view name, int64_t defaultValue); + + float GetFloat(std::string_view name, float defaultValue); + + double GetDouble(std::string_view name, double defaultValue); + + std::string GetString(std::string_view name, std::string_view defaultValue); + + std::vector GetRaw(std::string_view name, + std::string_view typeString, + std::span defaultValue); + + void SetBoolean(std::string_view name, bool value); + + void SetInteger(std::string_view name, int64_t value); + + void SetFloat(std::string_view name, float value); + + void SetDouble(std::string_view name, double value); + + void PublishBoolean(std::string_view name, std::function supplier); + + void PublishInteger(std::string_view name, std::function supplier); + + void PublishFloat(std::string_view name, std::function supplier); + + void PublishDouble(std::string_view name, std::function supplier); + + [[nodiscard]] + std::function AddBooleanPublisher(std::string_view name); + + [[nodiscard]] + std::function AddIntegerPublisher(std::string_view name); + + [[nodiscard]] + std::function AddFloatPublisher(std::string_view name); + + [[nodiscard]] + std::function AddDoublePublisher(std::string_view name); + + void SubscribeBoolean(std::string_view name, + std::function consumer); + + void SubscribeInteger(std::string_view name, + std::function consumer); + + void SubscribeFloat(std::string_view name, + std::function consumer); + + void SubscribeDouble(std::string_view name, + std::function consumer); + + void SetString(std::string_view name, std::string_view value); + + void SetRaw(std::string_view name, std::string_view typeString, + std::span value); + + template + requires wpi::StructSerializable + void SetStruct(std::string_view name, const T& value, I... info); + + template + void SetProtobuf(std::string_view name, const T& value); + + void PublishString(std::string_view name, + std::function supplier); + + void PublishRaw(std::string_view name, std::string_view typeString, + std::function()> supplier); + + void PublishRawSmall( + std::string_view name, std::string_view typeString, + std::function(wpi::SmallVectorImpl& buf)> + supplier); + + template + requires wpi::StructSerializable + void PublishStruct(std::string_view name, std::function supplier, + I... info); + + template + void PublishProtobuf(std::string_view name, std::function supplier); + + [[nodiscard]] + std::function AddStringPublisher( + std::string_view name); + + [[nodiscard]] + std::function)> AddRawPublisher( + std::string_view name, std::string_view typeString); + + template + requires wpi::StructSerializable + [[nodiscard]] + std::function AddStructPublisher(std::string_view name, + I... info); + + template + [[nodiscard]] + std::function AddProtobufPublisher(std::string_view name); + + void SubscribeString(std::string_view name, + std::function consumer); + + void SubscribeRaw(std::string_view name, std::string_view typeString, + std::function)> consumer); + + template + requires wpi::StructSerializable + void SubscribeStruct(std::string_view name, std::function consumer, + I... info); + + template + void SubscribeProtobuf(std::string_view name, + std::function consumer); + + template + requires SendableSerializableMoveTracked + SendableTable AddSendable(std::string_view name, T* obj, I... info); + + template + requires SendableSerializableSharedPointer + SendableTable AddSendable(std::string_view name, std::shared_ptr obj, + I... info); + + SendableTable GetChild(std::string_view name); + + void SetPublishOptions(std::string_view name, const SendableOptions& options); + + void SetSubscribeOptions(std::string_view name, + const SendableOptions& options); + + /** + * Gets the current value of a property (as a JSON object). + * + * @param name name + * @param propName property name + * @return JSON object; null object if the property does not exist. + */ + wpi::json GetProperty(std::string_view name, std::string_view propName) const; + + /** + * Sets a property value. + * + * @param name name + * @param propName property name + * @param value property value + */ + void SetProperty(std::string_view name, std::string_view propName, + const wpi::json& value); + + /** + * Deletes a property. Has no effect if the property does not exist. + * + * @param name name + * @param propName property name + */ + void DeleteProperty(std::string_view name, std::string_view propName); + + /** + * Gets all topic properties as a JSON object. Each key in the object + * is the property name, and the corresponding value is the property value. + * + * @param name name + * @return JSON object + */ + wpi::json GetProperties(std::string_view name) const; + + /** + * Updates multiple topic properties. Each key in the passed-in object is + * the name of the property to add/update, and the corresponding value is the + * property value to set for that property. Null values result in deletion + * of the corresponding property. + * + * @param name name + * @param properties JSON object with keys to add/update/delete + * @return False if properties is not an object + */ + bool SetProperties(std::string_view name, const wpi::json& properties); + + void Remove(std::string_view name); + + /** + * Return whether this sendable has been published. + * + * @return True if it has been published, false if not. + */ + bool IsPublished() const; + + /** + * Update the published values by calling the getters for all properties. + */ + void Update(); + + /** + * Erases all publishers and subscribers. + */ + void Clear(); + + /** + * Returns whether there is a data schema already registered with the given + * name. This does NOT perform a check as to whether the schema has already + * been published by another node on the network. + * + * @param name Name (the string passed as the data type for topics using this + * schema) + * @return True if schema already registered + */ + bool HasSchema(std::string_view name) const; + + /** + * Registers a data schema. Data schemas provide information for how a + * certain data type string can be decoded. The type string of a data schema + * indicates the type of the schema itself (e.g. "protobuf" for protobuf + * schemas, "struct" for struct schemas, etc). In NetworkTables, schemas are + * published just like normal topics, with the name being generated from the + * provided name: "/.schema/". Duplicate calls to this function with + * the same name are silently ignored. + * + * @param name Name (the string passed as the data type for topics using this + * schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + void AddSchema(std::string_view name, std::string_view type, + std::span schema); + + /** + * Registers a data schema. Data schemas provide information for how a + * certain data type string can be decoded. The type string of a data schema + * indicates the type of the schema itself (e.g. "protobuf" for protobuf + * schemas, "struct" for struct schemas, etc). In NetworkTables, schemas are + * published just like normal topics, with the name being generated from the + * provided name: "/.schema/". Duplicate calls to this function with + * the same name are silently ignored. + * + * @param name Name (the string passed as the data type for topics using this + * schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + void AddSchema(std::string_view name, std::string_view type, + std::string_view schema); + + /** + * Registers a protobuf schema. Duplicate calls to this function with the same + * name are silently ignored. + * + * @tparam T protobuf serializable type + * @param msg protobuf message + */ + template + void AddProtobufSchema(wpi::ProtobufMessage& msg); + + /** + * Registers a struct schema. Duplicate calls to this function with the same + * name are silently ignored. + * + * @tparam T struct serializable type + * @param info optional struct type info + */ + template + requires wpi::StructSerializable + void AddStructSchema(const I&... info); + + std::shared_ptr GetBackend() const { return m_backend; } + std::weak_ptr GetWeak() const { return m_backend; } + + protected: + SendableTable CreateSendable(std::string_view name, + std::unique_ptr sendable); + + std::shared_ptr m_backend; +}; + +} // namespace wpi2 + +#include "wpi/sendable2/SendableTable.inc" diff --git a/wpiutil/src/main/native/include/wpi/sendable2/SendableTable.inc b/wpiutil/src/main/native/include/wpi/sendable2/SendableTable.inc new file mode 100644 index 00000000000..d83cfd819ad --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/sendable2/SendableTable.inc @@ -0,0 +1,304 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include +#include +#include +#include + +#include "wpi/protobuf/Protobuf.h" +#include "wpi/struct/Struct.h" +#include "wpi/sendable2/SendableTable.h" +#include "wpi/MoveTracker.h" +#include "wpi/SmallVector.h" + +namespace wpi2 { + +class SendableWrapper { + public: + virtual ~SendableWrapper() = default; + + virtual std::string_view GetTypeString() const = 0; + + virtual void Init(SendableTable& table) const = 0; + + virtual bool Exists(SendableTable& table) = 0; +}; + +namespace detail { +template +class SendableWrapperMoveTracked final : public SendableWrapper { + explicit SendableWrapperMoveTracked(T* obj, SendableTable& table, I... info) + : m_last{obj}, + m_obj{obj}, + m_backend{table.GetWeak()}, + m_info{std::move(info)...} {} + + ~SendableWrapperMoveTracked() override { + if (T* obj = m_obj.Get()) { + if (auto backend = m_backend.lock()) { + SendableTable table{std::move(backend)}; + std::apply([&](I... info) { CloseSendable(obj, table, info...); }, + m_info); + } + } + } + + std::string_view GetTypeString() const override { + return std::apply([&](I... info) { GetSendableTypeString(info...); }, + m_info); + } + + void Init(SendableTable& table) const override { + if (T* obj = m_obj.Get()) { + std::apply([&](I... info) { InitSendable(obj, table, info...); }, m_info); + } + } + + bool Exists(SendableTable& table) override { + if (T* obj = m_obj.Get()) { + // reinit if moved + if (m_last != obj) { + m_last = obj; + std::apply([&](I... info) { InitSendable(obj, table, info...); }, + m_info); + } + return true; + } else { + return false; + } + } + + private: + T* m_last; + wpi::MoveWeakPtr m_obj; + std::weak_ptr m_backend; + [[no_unique_address]] std::tuple m_info; +}; + +template +class SendableWrapperSharedPtr final : public SendableWrapper { + explicit SendableWrapperSharedPtr(std::shared_ptr obj, + SendableTable& table, I... info) + : m_obj{std::move(obj)}, + m_backend{table.GetWeak()}, + m_info{std::move(info)...} {} + + ~SendableWrapperSharedPtr() override { + if (auto backend = m_backend.lock()) { + SendableTable table{std::move(backend)}; + std::apply([&](I... info) { CloseSendable(m_obj, table, info...); }, + m_info); + } + } + + std::string_view GetTypeString() const override { + return std::apply([&](I... info) { GetSendableTypeString(info...); }, + m_info); + } + + void Init(SendableTable& table) const override { + std::apply([&](I... info) { InitSendable(m_obj, table, info...); }, m_info); + } + + bool Exists(SendableTable& table) override { return true; } + + private: + std::shared_ptr m_obj; + std::weak_ptr m_backend; + [[no_unique_address]] std::tuple m_info; +}; +} // namespace detail + +template + requires wpi::StructSerializable +void SendableTable::SetStruct(std::string_view name, const T& value, + I... info) { + using S = wpi::Struct; + AddStructSchema(info...); + if constexpr (sizeof...(I) == 0) { + if constexpr (wpi::is_constexpr([] { S::GetSize(); })) { + uint8_t buf[S::GetSize()]; + S::Pack(buf, value); + SetRaw(name, S::GetTypeString(), buf); + return; + } + } + wpi::SmallVector buf; + buf.resize_for_overwrite(S::GetSize(info...)); + S::Pack(buf, value, info...); + SetRaw(name, S::GetTypeString(info...), buf); +} + +template +void SendableTable::SetProtobuf(std::string_view name, const T& value) { + wpi::ProtobufMessage msg; + AddProtobufSchema(msg); + wpi::SmallVector buf; + msg.Pack(buf, value); + SetRaw(name, msg.GetTypeString(), buf); +} + +template + requires wpi::StructSerializable +void SendableTable::PublishStruct(std::string_view name, + std::function supplier, I... info) { + using S = wpi::Struct; + AddStructSchema(info...); + PublishRawSmall( + name, S::GetTypeString(info...), + [supplier = std::move(supplier), info = std::tuple{info...}]( + wpi::SmallVectorImpl& buf) -> std::span { + std::apply( + [&](const I&... info) { + buf.resize_for_overwrite(S::GetSize(info...)); + S::Pack(buf, supplier(), info...); + }, + info); + return buf; + }); +} + +template +void SendableTable::PublishProtobuf(std::string_view name, + std::function supplier) { + wpi::ProtobufMessage msg; + AddProtobufSchema(msg); + auto typeString = msg.GetTypeString(); + PublishRawSmall( + name, typeString, + [supplier = std::move(supplier), msg = std::move(msg)]( + wpi::SmallVectorImpl& buf) mutable -> std::span { + msg.Pack(buf, supplier()); + return buf; + }); +} + +template + requires wpi::StructSerializable +std::function SendableTable::AddStructPublisher( + std::string_view name, I... info) { + using S = wpi::Struct; + AddStructSchema(info...); + return [rawSetter = AddRawPublisher(name, S::GetTypeString(info...)), + info = std::tuple{info...}](const T& value) { + if constexpr (sizeof...(I) == 0) { + if constexpr (wpi::is_constexpr([] { S::GetSize(); })) { + uint8_t buf[S::GetSize()]; + S::Pack(buf, value); + rawSetter(buf); + return; + } + } + wpi::SmallVector buf; + std::apply( + [&](const I&... info) { + buf.resize_for_overwrite(S::GetSize(info...)); + S::Pack(buf, value, info...); + }, + info); + rawSetter(buf); + }; +} + +template +[[nodiscard]] +std::function SendableTable::AddProtobufPublisher( + std::string_view name) { + wpi::ProtobufMessage msg; + AddProtobufSchema(msg); + auto typeString = msg.GetTypeString(); + return [rawSetter = AddRawPublisher(name, typeString), + msg = std::move(msg)](const T& value) mutable { + wpi::SmallVector buf; + msg.Pack(buf, value); + rawSetter(buf); + }; +} + +template + requires wpi::StructSerializable +void SendableTable::SubscribeStruct(std::string_view name, + std::function consumer, + I... info) { + using S = wpi::Struct; + AddStructSchema(info...); + SubscribeRaw( + name, S::GetTypeString(info...), + [consumer = std::move(consumer), + info = std::tuple{info...}](std::span data) { + if (data.size() >= std::apply(S::GetSize, info)) { + consumer(std::apply( + [&](const I&... info) { return S::Unpack(data, info...); }, + info)); + } + }); +} + +template +void SendableTable::SubscribeProtobuf(std::string_view name, + std::function consumer) { + wpi::ProtobufMessage msg; + AddProtobufSchema(msg); + auto typeString = msg.GetTypeString(); + SubscribeRaw(name, typeString, + [consumer = std::move(consumer), + msg = std::move(msg)](std::span data) mutable { + if (auto val = msg.Unpack(data)) { + consumer(*val); + } + }); +} + +template + requires SendableSerializableMoveTracked +inline SendableTable SendableTable::AddSendable(std::string_view name, T* obj, + I... info) { + return CreateSendable( + name, std::make_unique>( + obj, *this, std::move(info)...)); +} + +template + requires SendableSerializableSharedPointer +inline SendableTable SendableTable::AddSendable(std::string_view name, + std::shared_ptr obj, + I... info) { + return CreateSendable( + name, std::make_unique>( + std::move(obj), *this, std::move(info)...)); +} + +// Suppress unused-lambda-capture warning on AddSchema() call +#ifdef __clang__ +#pragma clang diagnostic push +#pragma clang diagnostic ignored "-Wunused-lambda-capture" +#endif + +template +void SendableTable::AddProtobufSchema(wpi::ProtobufMessage& msg) { + msg.ForEachProtobufDescriptor( + [this](auto typeString) { return HasSchema(typeString); }, + [this](auto typeString, auto schema) { + AddSchema(typeString, "proto:FileDescriptorProto", schema); + }); +} + +template + requires wpi::StructSerializable +void SendableTable::AddStructSchema(const I&... info) { + wpi::ForEachStructSchema( + [this](auto typeString, auto schema) { + AddSchema(typeString, "structschema", schema); + }, + info...); +} + +#ifdef __clang__ +#pragma clang diagnostic pop +#endif + +} // namespace wpi2 diff --git a/wpiutil/src/main/native/include/wpi/sendable2/SendableTableBackend.h b/wpiutil/src/main/native/include/wpi/sendable2/SendableTableBackend.h new file mode 100644 index 00000000000..c72cd56b88b --- /dev/null +++ b/wpiutil/src/main/native/include/wpi/sendable2/SendableTableBackend.h @@ -0,0 +1,251 @@ +// Copyright (c) FIRST and other WPILib contributors. +// Open Source Software; you can modify and/or share it under the terms of +// the WPILib BSD license file in the root directory of this project. + +#pragma once + +#include + +#include +#include +#include +#include +#include +#include + +#include "wpi/json_fwd.h" +#include "wpi/sendable2/SendableOptions.h" + +namespace wpi { +template +class SmallVectorImpl; +} // namespace wpi + +namespace wpi2 { + +class SendableWrapper; + +class SendableTableBackend { + public: + SendableTableBackend() = default; + virtual ~SendableTableBackend() = default; + SendableTableBackend(const SendableTableBackend&) = delete; + SendableTableBackend& operator=(const SendableTableBackend&) = delete; + + virtual bool GetBoolean(std::string_view name, bool defaultValue) = 0; + + virtual int64_t GetInteger(std::string_view name, int64_t defaultValue) = 0; + + virtual float GetFloat(std::string_view name, float defaultValue) = 0; + + virtual double GetDouble(std::string_view name, double defaultValue) = 0; + + virtual std::string GetString(std::string_view name, + std::string_view defaultValue) = 0; + + virtual std::vector GetRaw( + std::string_view name, std::string_view typeString, + std::span defaultValue) = 0; + + virtual void SetBoolean(std::string_view name, bool value) = 0; + + virtual void SetInteger(std::string_view name, int64_t value) = 0; + + virtual void SetFloat(std::string_view name, float value) = 0; + + virtual void SetDouble(std::string_view name, double value) = 0; + + virtual void SetString(std::string_view name, std::string_view value) = 0; + + virtual void SetRaw(std::string_view name, std::string_view typeString, + std::span value) = 0; + + virtual void PublishBoolean(std::string_view name, + std::function supplier) = 0; + + virtual void PublishInteger(std::string_view name, + std::function supplier) = 0; + + virtual void PublishFloat(std::string_view name, + std::function supplier) = 0; + + virtual void PublishDouble(std::string_view name, + std::function supplier) = 0; + + virtual void PublishString(std::string_view name, + std::function supplier) = 0; + + virtual void PublishRaw(std::string_view name, std::string_view typeString, + std::function()> supplier) = 0; + + virtual void PublishRawSmall( + std::string_view name, std::string_view typeString, + std::function(wpi::SmallVectorImpl& buf)> + supplier) = 0; + + [[nodiscard]] + virtual std::function AddBooleanPublisher( + std::string_view name) = 0; + + [[nodiscard]] + virtual std::function AddIntegerPublisher( + std::string_view name) = 0; + + [[nodiscard]] + virtual std::function AddFloatPublisher( + std::string_view name) = 0; + + [[nodiscard]] + virtual std::function AddDoublePublisher( + std::string_view name) = 0; + + [[nodiscard]] + virtual std::function AddStringPublisher( + std::string_view name) = 0; + + [[nodiscard]] + virtual std::function)> AddRawPublisher( + std::string_view name, std::string_view typeString) = 0; + + virtual void SubscribeBoolean(std::string_view name, + std::function consumer) = 0; + + virtual void SubscribeInteger(std::string_view name, + std::function consumer) = 0; + + virtual void SubscribeFloat(std::string_view name, + std::function consumer) = 0; + + virtual void SubscribeDouble(std::string_view name, + std::function consumer) = 0; + + virtual void SubscribeString( + std::string_view name, + std::function consumer) = 0; + + virtual void SubscribeRaw( + std::string_view name, std::string_view typeString, + std::function)> consumer) = 0; + + virtual std::shared_ptr CreateSendable( + std::string_view name, std::unique_ptr sendable) = 0; + + virtual std::shared_ptr GetChild( + std::string_view name) = 0; + + virtual void SetPublishOptions(std::string_view name, + const SendableOptions& options) = 0; + + virtual void SetSubscribeOptions(std::string_view name, + const SendableOptions& options) = 0; + + /** + * Gets the current value of a property (as a JSON object). + * + * @param name name + * @param propName property name + * @return JSON object; null object if the property does not exist. + */ + virtual wpi::json GetProperty(std::string_view name, + std::string_view propName) const = 0; + + /** + * Sets a property value. + * + * @param name name + * @param propName property name + * @param value property value + */ + virtual void SetProperty(std::string_view name, std::string_view propName, + const wpi::json& value) = 0; + + /** + * Deletes a property. Has no effect if the property does not exist. + * + * @param name name + * @param propName property name + */ + virtual void DeleteProperty(std::string_view name, + std::string_view propName) = 0; + + /** + * Gets all topic properties as a JSON object. Each key in the object + * is the property name, and the corresponding value is the property value. + * + * @param name name + * @return JSON object + */ + virtual wpi::json GetProperties(std::string_view name) const = 0; + + /** + * Updates multiple topic properties. Each key in the passed-in object is + * the name of the property to add/update, and the corresponding value is the + * property value to set for that property. Null values result in deletion + * of the corresponding property. + * + * @param name name + * @param properties JSON object with keys to add/update/delete + * @return False if properties is not an object + */ + virtual bool SetProperties(std::string_view name, + const wpi::json& properties) = 0; + + virtual void Remove(std::string_view name) = 0; + + /** + * Return whether this sendable has been published. + * + * @return True if it has been published, false if not. + */ + virtual bool IsPublished() const = 0; + + /** + * Update the published values by calling the getters for all properties. + */ + virtual void Update() = 0; + + /** + * Erases all publishers and subscribers. + */ + virtual void Clear() = 0; + + /** + * Returns whether there is a data schema already registered with the given + * name. + * + * @param name Name (the string passed as the data type for topics using this + * schema) + * @return True if schema already registered + */ + virtual bool HasSchema(std::string_view name) const = 0; + + /** + * Registers a data schema. Data schemas provide information for how a + * certain data type string can be decoded. The type string of a data schema + * indicates the type of the schema itself (e.g. "protobuf" for protobuf + * schemas, "struct" for struct schemas, etc). + * + * @param name Name (the string passed as the data type for topics using this + * schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + virtual void AddSchema(std::string_view name, std::string_view type, + std::span schema) = 0; + + /** + * Registers a data schema. Data schemas provide information for how a + * certain data type string can be decoded. The type string of a data schema + * indicates the type of the schema itself (e.g. "protobuf" for protobuf + * schemas, "struct" for struct schemas, etc). + * + * @param name Name (the string passed as the data type for topics using this + * schema) + * @param type Type of schema (e.g. "protobuf", "struct", etc) + * @param schema Schema data + */ + virtual void AddSchema(std::string_view name, std::string_view type, + std::string_view schema) = 0; +}; + +} // namespace wpi2