m_entries = new ConcurrentHashMap<>();
/**
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufEntry.java b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufEntry.java
new file mode 100644
index 0000000000..3f82c7ace4
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufEntry.java
@@ -0,0 +1,17 @@
+// 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;
+
+/**
+ * NetworkTables protobuf-encoded value entry.
+ *
+ * Unlike NetworkTableEntry, the entry goes away when close() is called.
+ *
+ * @param value class
+ */
+public interface ProtobufEntry extends ProtobufSubscriber, ProtobufPublisher {
+ /** Stops publishing the entry if it's published. */
+ void unpublish();
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufEntryImpl.java b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufEntryImpl.java
new file mode 100644
index 0000000000..b4359eaf03
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufEntryImpl.java
@@ -0,0 +1,209 @@
+// 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.protobuf.ProtobufBuffer;
+import java.io.IOException;
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+
+/**
+ * NetworkTables protobuf-encoded value implementation.
+ *
+ * @param value class
+ */
+final class ProtobufEntryImpl extends EntryBase implements ProtobufEntry {
+ /**
+ * Constructor.
+ *
+ * @param topic Topic
+ * @param handle Native handle
+ * @param defaultValue Default value for get()
+ */
+ ProtobufEntryImpl(
+ ProtobufTopic topic,
+ ProtobufBuffer buf,
+ int handle,
+ T defaultValue,
+ boolean schemaPublished) {
+ super(handle);
+ m_topic = topic;
+ m_defaultValue = defaultValue;
+ m_buf = buf;
+ m_schemaPublished = schemaPublished;
+ }
+
+ @Override
+ public ProtobufTopic getTopic() {
+ return m_topic;
+ }
+
+ @Override
+ public T get() {
+ return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), m_defaultValue);
+ }
+
+ @Override
+ public T get(T defaultValue) {
+ return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), defaultValue);
+ }
+
+ @Override
+ public boolean getInto(T out) {
+ byte[] raw = NetworkTablesJNI.getRaw(m_handle, m_emptyRaw);
+ if (raw.length == 0) {
+ return false;
+ }
+ try {
+ synchronized (m_buf) {
+ m_buf.readInto(out, raw);
+ return true;
+ }
+ } catch (IOException e) {
+ // ignored
+ }
+ return false;
+ }
+
+ @Override
+ public TimestampedObject getAtomic() {
+ return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), m_defaultValue);
+ }
+
+ @Override
+ public TimestampedObject getAtomic(T defaultValue) {
+ return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), defaultValue);
+ }
+
+ @Override
+ public TimestampedObject[] readQueue() {
+ TimestampedRaw[] raw = NetworkTablesJNI.readQueueRaw(m_handle);
+ @SuppressWarnings("unchecked")
+ TimestampedObject[] arr = (TimestampedObject[]) new TimestampedObject>[raw.length];
+ int err = 0;
+ for (int i = 0; i < raw.length; i++) {
+ arr[i] = fromRaw(raw[i], null);
+ if (arr[i].value == null) {
+ err++;
+ }
+ }
+
+ // discard bad values
+ if (err > 0) {
+ @SuppressWarnings("unchecked")
+ TimestampedObject[] newArr =
+ (TimestampedObject[]) new TimestampedObject>[raw.length - err];
+ int i = 0;
+ for (TimestampedObject e : arr) {
+ if (e.value != null) {
+ arr[i] = e;
+ i++;
+ }
+ }
+ arr = newArr;
+ }
+
+ return arr;
+ }
+
+ @Override
+ public T[] readQueueValues() {
+ byte[][] raw = NetworkTablesJNI.readQueueValuesRaw(m_handle);
+ @SuppressWarnings("unchecked")
+ T[] arr = (T[]) Array.newInstance(m_topic.getProto().getTypeClass(), raw.length);
+ int err = 0;
+ for (int i = 0; i < raw.length; i++) {
+ arr[i] = fromRaw(raw[i], null);
+ if (arr[i] == null) {
+ err++;
+ }
+ }
+
+ // discard bad values
+ if (err > 0) {
+ @SuppressWarnings("unchecked")
+ T[] newArr = (T[]) Array.newInstance(m_topic.getProto().getTypeClass(), raw.length - err);
+ int i = 0;
+ for (T e : arr) {
+ if (e != null) {
+ arr[i] = e;
+ i++;
+ }
+ }
+ arr = newArr;
+ }
+
+ return arr;
+ }
+
+ @Override
+ public void set(T value, long time) {
+ try {
+ synchronized (m_buf) {
+ if (!m_schemaPublished) {
+ m_schemaPublished = true;
+ m_topic.getInstance().addSchema(m_buf.getProto());
+ }
+ ByteBuffer bb = m_buf.write(value);
+ NetworkTablesJNI.setRaw(m_handle, time, bb, 0, bb.position());
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ @Override
+ public void setDefault(T value) {
+ try {
+ synchronized (m_buf) {
+ if (!m_schemaPublished) {
+ m_schemaPublished = true;
+ m_topic.getInstance().addSchema(m_buf.getProto());
+ }
+ ByteBuffer bb = m_buf.write(value);
+ NetworkTablesJNI.setDefaultRaw(m_handle, 0, bb, 0, bb.position());
+ }
+ } catch (IOException e) {
+ // ignore
+ }
+ }
+
+ @Override
+ public void unpublish() {
+ NetworkTablesJNI.unpublish(m_handle);
+ }
+
+ private T fromRaw(byte[] raw, T defaultValue) {
+ if (raw.length == 0) {
+ return defaultValue;
+ }
+ try {
+ synchronized (m_buf) {
+ return m_buf.read(raw);
+ }
+ } catch (IOException e) {
+ return defaultValue;
+ }
+ }
+
+ private TimestampedObject fromRaw(TimestampedRaw raw, T defaultValue) {
+ if (raw.value.length == 0) {
+ return new TimestampedObject(0, 0, defaultValue);
+ }
+ try {
+ synchronized (m_buf) {
+ return new TimestampedObject(raw.timestamp, raw.serverTime, m_buf.read(raw.value));
+ }
+ } catch (IOException e) {
+ return new TimestampedObject(0, 0, defaultValue);
+ }
+ }
+
+ private final ProtobufTopic m_topic;
+ private final T m_defaultValue;
+ private final ProtobufBuffer m_buf;
+ private boolean m_schemaPublished;
+ private static final byte[] m_emptyRaw = new byte[] {};
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufPublisher.java b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufPublisher.java
new file mode 100644
index 0000000000..ab22b86d13
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufPublisher.java
@@ -0,0 +1,52 @@
+// 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 java.util.function.Consumer;
+
+/**
+ * NetworkTables protobuf-encoded value publisher.
+ *
+ * @param value class
+ */
+public interface ProtobufPublisher extends Publisher, Consumer {
+ /**
+ * Get the corresponding topic.
+ *
+ * @return Topic
+ */
+ @Override
+ ProtobufTopic getTopic();
+
+ /**
+ * Publish a new value using current NT time.
+ *
+ * @param value value to publish
+ */
+ default void set(T value) {
+ set(value, 0);
+ }
+
+ /**
+ * Publish a new value.
+ *
+ * @param value value to publish
+ * @param time timestamp; 0 indicates current NT time should be used
+ */
+ void set(T value, long time);
+
+ /**
+ * Publish a default value. On reconnect, a default value will never be used in preference to a
+ * published value.
+ *
+ * @param value value
+ */
+ void setDefault(T value);
+
+ @Override
+ default void accept(T value) {
+ set(value);
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufSubscriber.java b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufSubscriber.java
new file mode 100644
index 0000000000..2236ce9a32
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufSubscriber.java
@@ -0,0 +1,94 @@
+// 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 java.util.function.Supplier;
+
+/**
+ * NetworkTables protobuf-encoded value subscriber.
+ *
+ * @param value class
+ */
+@SuppressWarnings("PMD.MissingOverride")
+public interface ProtobufSubscriber extends Subscriber, Supplier {
+ /**
+ * Get the corresponding topic.
+ *
+ * @return Topic
+ */
+ @Override
+ ProtobufTopic getTopic();
+
+ /**
+ * Get the last published value. If no value has been published or the value cannot be unpacked,
+ * returns the stored default value.
+ *
+ * @return value
+ */
+ T get();
+
+ /**
+ * Get the last published value. If no value has been published or the value cannot be unpacked,
+ * returns the passed defaultValue.
+ *
+ * @param defaultValue default value to return if no value has been published
+ * @return value
+ */
+ T get(T defaultValue);
+
+ /**
+ * Get the last published value, replacing the contents in place of an existing object. If no
+ * value has been published or the value cannot be unpacked, does not replace the contents and
+ * returns false. This function will not work (will throw UnsupportedOperationException) unless T
+ * is mutable (and the implementation of Struct implements unpackInto).
+ *
+ * Note: due to Java language limitations, it's not possible to validate at compile time that
+ * the out parameter is mutable.
+ *
+ * @param out object to replace contents of; must be mutable
+ * @return true if successful
+ * @throws UnsupportedOperationException if T is immutable
+ */
+ boolean getInto(T out);
+
+ /**
+ * Get the last published value along with its timestamp. If no value has been published or the
+ * value cannot be unpacked, returns the stored default value and a timestamp of 0.
+ *
+ * @return timestamped value
+ */
+ TimestampedObject getAtomic();
+
+ /**
+ * Get the last published value along with its timestamp. If no value has been published or the
+ * value cannot be unpacked, returns the passed defaultValue and a timestamp of 0.
+ *
+ * @param defaultValue default value to return if no value has been published
+ * @return timestamped value
+ */
+ TimestampedObject getAtomic(T defaultValue);
+
+ /**
+ * Get an array of all valid value changes since the last call to readQueue. Also provides a
+ * timestamp for each value. Values that cannot be unpacked are dropped.
+ *
+ * The "poll storage" subscribe option can be used to set the queue depth.
+ *
+ * @return Array of timestamped values; empty array if no valid new changes have been published
+ * since the previous call.
+ */
+ TimestampedObject[] readQueue();
+
+ /**
+ * Get an array of all valid value changes since the last call to readQueue. Values that cannot be
+ * unpacked are dropped.
+ *
+ * The "poll storage" subscribe option can be used to set the queue depth.
+ *
+ * @return Array of values; empty array if no valid new changes have been published since the
+ * previous call.
+ */
+ T[] readQueueValues();
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufTopic.java b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufTopic.java
new file mode 100644
index 0000000000..c3dad133a5
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/ProtobufTopic.java
@@ -0,0 +1,178 @@
+// 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.protobuf.Protobuf;
+import edu.wpi.first.util.protobuf.ProtobufBuffer;
+
+/**
+ * NetworkTables protobuf-encoded value topic.
+ *
+ * @param value class
+ */
+public final class ProtobufTopic extends Topic {
+ private ProtobufTopic(Topic topic, Protobuf proto) {
+ super(topic.m_inst, topic.m_handle);
+ m_proto = proto;
+ }
+
+ private ProtobufTopic(NetworkTableInstance inst, int handle, Protobuf proto) {
+ super(inst, handle);
+ m_proto = proto;
+ }
+
+ /**
+ * Create a ProtobufTopic from a generic topic.
+ *
+ * @param value class (inferred from proto)
+ * @param topic generic topic
+ * @param proto protobuf serialization implementation
+ * @return ProtobufTopic for value class
+ */
+ public static ProtobufTopic wrap(Topic topic, Protobuf proto) {
+ return new ProtobufTopic(topic, proto);
+ }
+
+ /**
+ * Create a ProtobufTopic from a native handle; generally NetworkTableInstance.getProtobufTopic()
+ * should be used instead.
+ *
+ * @param value class (inferred from proto)
+ * @param inst Instance
+ * @param handle Native handle
+ * @param proto protobuf serialization implementation
+ * @return ProtobufTopic for value class
+ */
+ public static ProtobufTopic wrap(
+ NetworkTableInstance inst, int handle, Protobuf proto) {
+ return new ProtobufTopic(inst, handle, proto);
+ }
+
+ /**
+ * Create a new subscriber to the topic.
+ *
+ * The subscriber is only active as long as the returned object is not closed.
+ *
+ *
Subscribers that do not match the published data type do not return any values. To determine
+ * if the data type matches, use the appropriate Topic functions.
+ *
+ * @param defaultValue default value used when a default is not provided to a getter function
+ * @param options subscribe options
+ * @return subscriber
+ */
+ public ProtobufSubscriber subscribe(T defaultValue, PubSubOption... options) {
+ return new ProtobufEntryImpl(
+ this,
+ ProtobufBuffer.create(m_proto),
+ NetworkTablesJNI.subscribe(
+ m_handle, NetworkTableType.kRaw.getValue(), m_proto.getTypeString(), options),
+ defaultValue,
+ false);
+ }
+
+ /**
+ * Create a new publisher to the topic.
+ *
+ * The publisher is only active as long as the returned object is not closed.
+ *
+ *
It is not possible to publish two different data types to the same topic. Conflicts between
+ * publishers are typically resolved by the server on a first-come, first-served basis. Any
+ * published values that do not match the topic's data type are dropped (ignored). To determine if
+ * the data type matches, use the appropriate Topic functions.
+ *
+ * @param options publish options
+ * @return publisher
+ */
+ public ProtobufPublisher publish(PubSubOption... options) {
+ m_inst.addSchema(m_proto);
+ return new ProtobufEntryImpl(
+ this,
+ ProtobufBuffer.create(m_proto),
+ NetworkTablesJNI.publish(
+ m_handle, NetworkTableType.kRaw.getValue(), m_proto.getTypeString(), options),
+ null,
+ true);
+ }
+
+ /**
+ * Create a new publisher to the topic, with type string and initial properties.
+ *
+ * The publisher is only active as long as the returned object is not closed.
+ *
+ *
It is not possible to publish two different data types to the same topic. Conflicts between
+ * publishers are typically resolved by the server on a first-come, first-served basis. Any
+ * published values that do not match the topic's data type are dropped (ignored). To determine if
+ * the data type matches, use the appropriate Topic functions.
+ *
+ * @param properties JSON properties
+ * @param options publish options
+ * @return publisher
+ * @throws IllegalArgumentException if properties is not a JSON object
+ */
+ public ProtobufPublisher publishEx(String properties, PubSubOption... options) {
+ m_inst.addSchema(m_proto);
+ return new ProtobufEntryImpl(
+ this,
+ ProtobufBuffer.create(m_proto),
+ NetworkTablesJNI.publishEx(
+ m_handle,
+ NetworkTableType.kRaw.getValue(),
+ m_proto.getTypeString(),
+ properties,
+ options),
+ null,
+ true);
+ }
+
+ /**
+ * Create a new entry for the topic.
+ *
+ * Entries act as a combination of a subscriber and a weak publisher. The subscriber is active
+ * as long as the entry is not closed. The publisher is created when the entry is first written
+ * to, and remains active until either unpublish() is called or the entry is closed.
+ *
+ *
It is not possible to use two different data types with the same topic. Conflicts between
+ * publishers are typically resolved by the server on a first-come, first-served basis. Any
+ * published values that do not match the topic's data type are dropped (ignored), and the entry
+ * will show no new values if the data type does not match. To determine if the data type matches,
+ * use the appropriate Topic functions.
+ *
+ * @param defaultValue default value used when a default is not provided to a getter function
+ * @param options publish and/or subscribe options
+ * @return entry
+ */
+ public ProtobufEntry getEntry(T defaultValue, PubSubOption... options) {
+ return new ProtobufEntryImpl(
+ this,
+ ProtobufBuffer.create(m_proto),
+ NetworkTablesJNI.getEntry(
+ m_handle, NetworkTableType.kRaw.getValue(), m_proto.getTypeString(), options),
+ defaultValue,
+ false);
+ }
+
+ public Protobuf getProto() {
+ return m_proto;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof ProtobufTopic)) {
+ return false;
+ }
+
+ return super.equals(other) && m_proto == ((ProtobufTopic>) other).m_proto;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() ^ m_proto.hashCode();
+ }
+
+ private final Protobuf m_proto;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayEntry.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayEntry.java
new file mode 100644
index 0000000000..4cc9e8593d
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayEntry.java
@@ -0,0 +1,17 @@
+// 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;
+
+/**
+ * NetworkTables struct-encoded array value entry.
+ *
+ * Unlike NetworkTableEntry, the entry goes away when close() is called.
+ *
+ * @param value class
+ */
+public interface StructArrayEntry extends StructArraySubscriber, StructArrayPublisher {
+ /** Stops publishing the entry if it's published. */
+ void unpublish();
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayEntryImpl.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayEntryImpl.java
new file mode 100644
index 0000000000..4e8a4a0495
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayEntryImpl.java
@@ -0,0 +1,197 @@
+// 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.struct.StructBuffer;
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+
+/**
+ * NetworkTables struct-encoded value implementation.
+ *
+ * @param value class
+ */
+@SuppressWarnings("PMD.ArrayIsStoredDirectly")
+final class StructArrayEntryImpl extends EntryBase implements StructArrayEntry {
+ /**
+ * Constructor.
+ *
+ * @param topic Topic
+ * @param handle Native handle
+ * @param defaultValue Default value for get()
+ */
+ StructArrayEntryImpl(
+ StructArrayTopic topic,
+ StructBuffer buf,
+ int handle,
+ T[] defaultValue,
+ boolean schemaPublished) {
+ super(handle);
+ m_topic = topic;
+ m_defaultValue = defaultValue;
+ m_buf = buf;
+ m_schemaPublished = schemaPublished;
+ }
+
+ @Override
+ public StructArrayTopic getTopic() {
+ return m_topic;
+ }
+
+ @Override
+ public T[] get() {
+ return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), m_defaultValue);
+ }
+
+ @Override
+ public T[] get(T[] defaultValue) {
+ return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), defaultValue);
+ }
+
+ @Override
+ public TimestampedObject getAtomic() {
+ return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), m_defaultValue);
+ }
+
+ @Override
+ public TimestampedObject getAtomic(T[] defaultValue) {
+ return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), defaultValue);
+ }
+
+ @Override
+ public TimestampedObject[] readQueue() {
+ TimestampedRaw[] raw = NetworkTablesJNI.readQueueRaw(m_handle);
+ @SuppressWarnings("unchecked")
+ TimestampedObject[] arr = (TimestampedObject[]) new TimestampedObject>[raw.length];
+ int err = 0;
+ for (int i = 0; i < raw.length; i++) {
+ arr[i] = fromRaw(raw[i], null);
+ if (arr[i].value == null) {
+ err++;
+ }
+ }
+
+ // discard bad values
+ if (err > 0) {
+ @SuppressWarnings("unchecked")
+ TimestampedObject[] newArr =
+ (TimestampedObject[]) new TimestampedObject>[raw.length - err];
+ int i = 0;
+ for (TimestampedObject e : arr) {
+ if (e.value != null) {
+ arr[i] = e;
+ i++;
+ }
+ }
+ arr = newArr;
+ }
+
+ return arr;
+ }
+
+ @Override
+ public T[][] readQueueValues() {
+ byte[][] raw = NetworkTablesJNI.readQueueValuesRaw(m_handle);
+ @SuppressWarnings("unchecked")
+ T[][] arr = (T[][]) Array.newInstance(Array.class, raw.length);
+ int err = 0;
+ for (int i = 0; i < raw.length; i++) {
+ arr[i] = fromRaw(raw[i], null);
+ if (arr[i] == null) {
+ err++;
+ }
+ }
+
+ // discard bad values
+ if (err > 0) {
+ @SuppressWarnings("unchecked")
+ T[][] newArr = (T[][]) Array.newInstance(Array.class, raw.length - err);
+ int i = 0;
+ for (T[] e : arr) {
+ if (e != null) {
+ arr[i] = e;
+ i++;
+ }
+ }
+ arr = newArr;
+ }
+
+ return arr;
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ @Override
+ public void set(T[] value, long time) {
+ try {
+ synchronized (m_buf) {
+ if (!m_schemaPublished) {
+ m_schemaPublished = true;
+ m_topic.getInstance().addSchema(m_buf.getStruct());
+ }
+ ByteBuffer bb = m_buf.writeArray(value);
+ NetworkTablesJNI.setRaw(m_handle, time, bb, 0, bb.position());
+ }
+ } catch (RuntimeException e) {
+ // ignore
+ }
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ @Override
+ public void setDefault(T[] value) {
+ try {
+ synchronized (m_buf) {
+ if (!m_schemaPublished) {
+ m_schemaPublished = true;
+ m_topic.getInstance().addSchema(m_buf.getStruct());
+ }
+ ByteBuffer bb = m_buf.writeArray(value);
+ NetworkTablesJNI.setDefaultRaw(m_handle, 0, bb, 0, bb.position());
+ }
+ } catch (RuntimeException e) {
+ // ignore
+ }
+ }
+
+ @Override
+ public void unpublish() {
+ NetworkTablesJNI.unpublish(m_handle);
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ private T[] fromRaw(byte[] raw, T[] defaultValue) {
+ if (raw.length == 0) {
+ return defaultValue;
+ }
+ try {
+ synchronized (m_buf) {
+ return m_buf.readArray(raw);
+ }
+ } catch (RuntimeException e) {
+ return defaultValue;
+ }
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ private TimestampedObject fromRaw(TimestampedRaw raw, T[] defaultValue) {
+ if (raw.value.length == 0) {
+ return new TimestampedObject(0, 0, defaultValue);
+ }
+ try {
+ synchronized (m_buf) {
+ return new TimestampedObject(
+ raw.timestamp, raw.serverTime, m_buf.readArray(raw.value));
+ }
+ } catch (RuntimeException e) {
+ return new TimestampedObject(0, 0, defaultValue);
+ }
+ }
+
+ private final StructArrayTopic m_topic;
+ private final T[] m_defaultValue;
+ private final StructBuffer m_buf;
+ private boolean m_schemaPublished;
+ private static final byte[] m_emptyRaw = new byte[] {};
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayPublisher.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayPublisher.java
new file mode 100644
index 0000000000..aa291e9d49
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayPublisher.java
@@ -0,0 +1,52 @@
+// 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 java.util.function.Consumer;
+
+/**
+ * NetworkTables struct-encoded array value publisher.
+ *
+ * @param value class
+ */
+public interface StructArrayPublisher extends Publisher, Consumer {
+ /**
+ * Get the corresponding topic.
+ *
+ * @return Topic
+ */
+ @Override
+ StructArrayTopic getTopic();
+
+ /**
+ * Publish a new value using current NT time.
+ *
+ * @param value value to publish
+ */
+ default void set(T[] value) {
+ set(value, 0);
+ }
+
+ /**
+ * Publish a new value.
+ *
+ * @param value value to publish
+ * @param time timestamp; 0 indicates current NT time should be used
+ */
+ void set(T[] value, long time);
+
+ /**
+ * Publish a default value. On reconnect, a default value will never be used in preference to a
+ * published value.
+ *
+ * @param value value
+ */
+ void setDefault(T[] value);
+
+ @Override
+ default void accept(T[] value) {
+ set(value);
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructArraySubscriber.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructArraySubscriber.java
new file mode 100644
index 0000000000..b4755e6d1c
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructArraySubscriber.java
@@ -0,0 +1,79 @@
+// 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 java.util.function.Supplier;
+
+/**
+ * NetworkTables struct-encoded array value subscriber.
+ *
+ * @param value class
+ */
+@SuppressWarnings("PMD.MissingOverride")
+public interface StructArraySubscriber extends Subscriber, Supplier {
+ /**
+ * Get the corresponding topic.
+ *
+ * @return Topic
+ */
+ @Override
+ StructArrayTopic getTopic();
+
+ /**
+ * Get the last published value. If no value has been published or the value cannot be unpacked,
+ * returns the stored default value.
+ *
+ * @return value
+ */
+ T[] get();
+
+ /**
+ * Get the last published value. If no value has been published or the value cannot be unpacked,
+ * returns the passed defaultValue.
+ *
+ * @param defaultValue default value to return if no value has been published
+ * @return value
+ */
+ T[] get(T[] defaultValue);
+
+ /**
+ * Get the last published value along with its timestamp. If no value has been published or the
+ * value cannot be unpacked, returns the stored default value and a timestamp of 0.
+ *
+ * @return timestamped value
+ */
+ TimestampedObject getAtomic();
+
+ /**
+ * Get the last published value along with its timestamp. If no value has been published or the
+ * value cannot be unpacked, returns the passed defaultValue and a timestamp of 0.
+ *
+ * @param defaultValue default value to return if no value has been published
+ * @return timestamped value
+ */
+ TimestampedObject getAtomic(T[] defaultValue);
+
+ /**
+ * Get an array of all valid value changes since the last call to readQueue. Also provides a
+ * timestamp for each value. Values that cannot be unpacked are dropped.
+ *
+ * The "poll storage" subscribe option can be used to set the queue depth.
+ *
+ * @return Array of timestamped values; empty array if no valid new changes have been published
+ * since the previous call.
+ */
+ TimestampedObject[] readQueue();
+
+ /**
+ * Get an array of all valid value changes since the last call to readQueue. Values that cannot be
+ * unpacked are dropped.
+ *
+ * The "poll storage" subscribe option can be used to set the queue depth.
+ *
+ * @return Array of values; empty array if no valid new changes have been published since the
+ * previous call.
+ */
+ T[][] readQueueValues();
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayTopic.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayTopic.java
new file mode 100644
index 0000000000..247501bbfa
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructArrayTopic.java
@@ -0,0 +1,178 @@
+// 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.struct.Struct;
+import edu.wpi.first.util.struct.StructBuffer;
+
+/**
+ * NetworkTables struct-encoded array value topic.
+ *
+ * @param value class
+ */
+public final class StructArrayTopic extends Topic {
+ private StructArrayTopic(Topic topic, Struct struct) {
+ super(topic.m_inst, topic.m_handle);
+ m_struct = struct;
+ }
+
+ private StructArrayTopic(NetworkTableInstance inst, int handle, Struct struct) {
+ super(inst, handle);
+ m_struct = struct;
+ }
+
+ /**
+ * Create a StructArrayTopic from a generic topic.
+ *
+ * @param value class (inferred from struct)
+ * @param topic generic topic
+ * @param struct struct serialization implementation
+ * @return StructArrayTopic for value class
+ */
+ public static StructArrayTopic wrap(Topic topic, Struct struct) {
+ return new StructArrayTopic(topic, struct);
+ }
+
+ /**
+ * Create a StructArrayTopic from a native handle; generally
+ * NetworkTableInstance.getStructArrayTopic() should be used instead.
+ *
+ * @param value class (inferred from struct)
+ * @param inst Instance
+ * @param handle Native handle
+ * @param struct struct serialization implementation
+ * @return StructArrayTopic for value class
+ */
+ public static StructArrayTopic wrap(
+ NetworkTableInstance inst, int handle, Struct struct) {
+ return new StructArrayTopic(inst, handle, struct);
+ }
+
+ /**
+ * Create a new subscriber to the topic.
+ *
+ * The subscriber is only active as long as the returned object is not closed.
+ *
+ *
Subscribers that do not match the published data type do not return any values. To determine
+ * if the data type matches, use the appropriate Topic functions.
+ *
+ * @param defaultValue default value used when a default is not provided to a getter function
+ * @param options subscribe options
+ * @return subscriber
+ */
+ public StructArraySubscriber subscribe(T[] defaultValue, PubSubOption... options) {
+ return new StructArrayEntryImpl(
+ this,
+ StructBuffer.create(m_struct),
+ NetworkTablesJNI.subscribe(
+ m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString() + "[]", options),
+ defaultValue,
+ false);
+ }
+
+ /**
+ * Create a new publisher to the topic.
+ *
+ * The publisher is only active as long as the returned object is not closed.
+ *
+ *
It is not possible to publish two different data types to the same topic. Conflicts between
+ * publishers are typically resolved by the server on a first-come, first-served basis. Any
+ * published values that do not match the topic's data type are dropped (ignored). To determine if
+ * the data type matches, use the appropriate Topic functions.
+ *
+ * @param options publish options
+ * @return publisher
+ */
+ public StructArrayPublisher publish(PubSubOption... options) {
+ m_inst.addSchema(m_struct);
+ return new StructArrayEntryImpl(
+ this,
+ StructBuffer.create(m_struct),
+ NetworkTablesJNI.publish(
+ m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString() + "[]", options),
+ null,
+ true);
+ }
+
+ /**
+ * Create a new publisher to the topic, with type string and initial properties.
+ *
+ * The publisher is only active as long as the returned object is not closed.
+ *
+ *
It is not possible to publish two different data types to the same topic. Conflicts between
+ * publishers are typically resolved by the server on a first-come, first-served basis. Any
+ * published values that do not match the topic's data type are dropped (ignored). To determine if
+ * the data type matches, use the appropriate Topic functions.
+ *
+ * @param properties JSON properties
+ * @param options publish options
+ * @return publisher
+ * @throws IllegalArgumentException if properties is not a JSON object
+ */
+ public StructArrayPublisher publishEx(String properties, PubSubOption... options) {
+ m_inst.addSchema(m_struct);
+ return new StructArrayEntryImpl(
+ this,
+ StructBuffer.create(m_struct),
+ NetworkTablesJNI.publishEx(
+ m_handle,
+ NetworkTableType.kRaw.getValue(),
+ m_struct.getTypeString() + "[]",
+ properties,
+ options),
+ null,
+ true);
+ }
+
+ /**
+ * Create a new entry for the topic.
+ *
+ * Entries act as a combination of a subscriber and a weak publisher. The subscriber is active
+ * as long as the entry is not closed. The publisher is created when the entry is first written
+ * to, and remains active until either unpublish() is called or the entry is closed.
+ *
+ *
It is not possible to use two different data types with the same topic. Conflicts between
+ * publishers are typically resolved by the server on a first-come, first-served basis. Any
+ * published values that do not match the topic's data type are dropped (ignored), and the entry
+ * will show no new values if the data type does not match. To determine if the data type matches,
+ * use the appropriate Topic functions.
+ *
+ * @param defaultValue default value used when a default is not provided to a getter function
+ * @param options publish and/or subscribe options
+ * @return entry
+ */
+ public StructArrayEntry getEntry(T[] defaultValue, PubSubOption... options) {
+ return new StructArrayEntryImpl(
+ this,
+ StructBuffer.create(m_struct),
+ NetworkTablesJNI.getEntry(
+ m_handle, NetworkTableType.kRaw.getValue(), m_struct.getTypeString() + "[]", options),
+ defaultValue,
+ false);
+ }
+
+ public Struct getStruct() {
+ return m_struct;
+ }
+
+ @Override
+ public boolean equals(Object other) {
+ if (other == this) {
+ return true;
+ }
+ if (!(other instanceof StructArrayTopic)) {
+ return false;
+ }
+
+ return super.equals(other) && m_struct == ((StructArrayTopic>) other).m_struct;
+ }
+
+ @Override
+ public int hashCode() {
+ return super.hashCode() ^ m_struct.hashCode();
+ }
+
+ private final Struct m_struct;
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructEntry.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructEntry.java
new file mode 100644
index 0000000000..e687fdb452
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructEntry.java
@@ -0,0 +1,17 @@
+// 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;
+
+/**
+ * NetworkTables struct-encoded value entry.
+ *
+ * Unlike NetworkTableEntry, the entry goes away when close() is called.
+ *
+ * @param value class
+ */
+public interface StructEntry extends StructSubscriber, StructPublisher {
+ /** Stops publishing the entry if it's published. */
+ void unpublish();
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructEntryImpl.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructEntryImpl.java
new file mode 100644
index 0000000000..bd02d27fe5
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructEntryImpl.java
@@ -0,0 +1,207 @@
+// 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.struct.StructBuffer;
+import java.lang.reflect.Array;
+import java.nio.ByteBuffer;
+
+/**
+ * NetworkTables struct-encoded value implementation.
+ *
+ * @param value class
+ */
+final class StructEntryImpl extends EntryBase implements StructEntry {
+ /**
+ * Constructor.
+ *
+ * @param topic Topic
+ * @param handle Native handle
+ * @param defaultValue Default value for get()
+ */
+ StructEntryImpl(
+ StructTopic topic,
+ StructBuffer buf,
+ int handle,
+ T defaultValue,
+ boolean schemaPublished) {
+ super(handle);
+ m_topic = topic;
+ m_defaultValue = defaultValue;
+ m_buf = buf;
+ m_schemaPublished = schemaPublished;
+ }
+
+ @Override
+ public StructTopic getTopic() {
+ return m_topic;
+ }
+
+ @Override
+ public T get() {
+ return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), m_defaultValue);
+ }
+
+ @Override
+ public T get(T defaultValue) {
+ return fromRaw(NetworkTablesJNI.getRaw(m_handle, m_emptyRaw), defaultValue);
+ }
+
+ @Override
+ public boolean getInto(T out) {
+ byte[] raw = NetworkTablesJNI.getRaw(m_handle, m_emptyRaw);
+ if (raw.length == 0) {
+ return false;
+ }
+ synchronized (m_buf) {
+ m_buf.readInto(out, raw);
+ return true;
+ }
+ }
+
+ @Override
+ public TimestampedObject getAtomic() {
+ return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), m_defaultValue);
+ }
+
+ @Override
+ public TimestampedObject getAtomic(T defaultValue) {
+ return fromRaw(NetworkTablesJNI.getAtomicRaw(m_handle, m_emptyRaw), defaultValue);
+ }
+
+ @Override
+ public TimestampedObject[] readQueue() {
+ TimestampedRaw[] raw = NetworkTablesJNI.readQueueRaw(m_handle);
+ @SuppressWarnings("unchecked")
+ TimestampedObject[] arr = (TimestampedObject[]) new TimestampedObject>[raw.length];
+ int err = 0;
+ for (int i = 0; i < raw.length; i++) {
+ arr[i] = fromRaw(raw[i], null);
+ if (arr[i].value == null) {
+ err++;
+ }
+ }
+
+ // discard bad values
+ if (err > 0) {
+ @SuppressWarnings("unchecked")
+ TimestampedObject[] newArr =
+ (TimestampedObject[]) new TimestampedObject>[raw.length - err];
+ int i = 0;
+ for (TimestampedObject e : arr) {
+ if (e.value != null) {
+ arr[i] = e;
+ i++;
+ }
+ }
+ arr = newArr;
+ }
+
+ return arr;
+ }
+
+ @Override
+ public T[] readQueueValues() {
+ byte[][] raw = NetworkTablesJNI.readQueueValuesRaw(m_handle);
+ @SuppressWarnings("unchecked")
+ T[] arr = (T[]) Array.newInstance(m_topic.getStruct().getTypeClass(), raw.length);
+ int err = 0;
+ for (int i = 0; i < raw.length; i++) {
+ arr[i] = fromRaw(raw[i], null);
+ if (arr[i] == null) {
+ err++;
+ }
+ }
+
+ // discard bad values
+ if (err > 0) {
+ @SuppressWarnings("unchecked")
+ T[] newArr = (T[]) Array.newInstance(m_topic.getStruct().getTypeClass(), raw.length - err);
+ int i = 0;
+ for (T e : arr) {
+ if (e != null) {
+ arr[i] = e;
+ i++;
+ }
+ }
+ arr = newArr;
+ }
+
+ return arr;
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ @Override
+ public void set(T value, long time) {
+ try {
+ synchronized (m_buf) {
+ if (!m_schemaPublished) {
+ m_schemaPublished = true;
+ m_topic.getInstance().addSchema(m_buf.getStruct());
+ }
+ ByteBuffer bb = m_buf.write(value);
+ NetworkTablesJNI.setRaw(m_handle, time, bb, 0, bb.position());
+ }
+ } catch (RuntimeException e) {
+ // ignore
+ }
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ @Override
+ public void setDefault(T value) {
+ try {
+ synchronized (m_buf) {
+ if (!m_schemaPublished) {
+ m_schemaPublished = true;
+ m_topic.getInstance().addSchema(m_buf.getStruct());
+ }
+ ByteBuffer bb = m_buf.write(value);
+ NetworkTablesJNI.setDefaultRaw(m_handle, 0, bb, 0, bb.position());
+ }
+ } catch (RuntimeException e) {
+ // ignore
+ }
+ }
+
+ @Override
+ public void unpublish() {
+ NetworkTablesJNI.unpublish(m_handle);
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ private T fromRaw(byte[] raw, T defaultValue) {
+ if (raw.length == 0) {
+ return defaultValue;
+ }
+ try {
+ synchronized (m_buf) {
+ return m_buf.read(raw);
+ }
+ } catch (RuntimeException e) {
+ return defaultValue;
+ }
+ }
+
+ @SuppressWarnings("PMD.AvoidCatchingGenericException")
+ private TimestampedObject fromRaw(TimestampedRaw raw, T defaultValue) {
+ if (raw.value.length == 0) {
+ return new TimestampedObject(0, 0, defaultValue);
+ }
+ try {
+ synchronized (m_buf) {
+ return new TimestampedObject(raw.timestamp, raw.serverTime, m_buf.read(raw.value));
+ }
+ } catch (RuntimeException e) {
+ return new TimestampedObject(0, 0, defaultValue);
+ }
+ }
+
+ private final StructTopic m_topic;
+ private final T m_defaultValue;
+ private final StructBuffer m_buf;
+ private boolean m_schemaPublished;
+ private static final byte[] m_emptyRaw = new byte[] {};
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructPublisher.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructPublisher.java
new file mode 100644
index 0000000000..ec766e4775
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructPublisher.java
@@ -0,0 +1,52 @@
+// 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 java.util.function.Consumer;
+
+/**
+ * NetworkTables struct-encoded value publisher.
+ *
+ * @param value class
+ */
+public interface StructPublisher extends Publisher, Consumer {
+ /**
+ * Get the corresponding topic.
+ *
+ * @return Topic
+ */
+ @Override
+ StructTopic getTopic();
+
+ /**
+ * Publish a new value using current NT time.
+ *
+ * @param value value to publish
+ */
+ default void set(T value) {
+ set(value, 0);
+ }
+
+ /**
+ * Publish a new value.
+ *
+ * @param value value to publish
+ * @param time timestamp; 0 indicates current NT time should be used
+ */
+ void set(T value, long time);
+
+ /**
+ * Publish a default value. On reconnect, a default value will never be used in preference to a
+ * published value.
+ *
+ * @param value value
+ */
+ void setDefault(T value);
+
+ @Override
+ default void accept(T value) {
+ set(value);
+ }
+}
diff --git a/ntcore/src/main/java/edu/wpi/first/networktables/StructSubscriber.java b/ntcore/src/main/java/edu/wpi/first/networktables/StructSubscriber.java
new file mode 100644
index 0000000000..b537b5cfc3
--- /dev/null
+++ b/ntcore/src/main/java/edu/wpi/first/networktables/StructSubscriber.java
@@ -0,0 +1,94 @@
+// 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 java.util.function.Supplier;
+
+/**
+ * NetworkTables struct-encoded value subscriber.
+ *
+ * @param value class
+ */
+@SuppressWarnings("PMD.MissingOverride")
+public interface StructSubscriber extends Subscriber, Supplier {
+ /**
+ * Get the corresponding topic.
+ *
+ * @return Topic
+ */
+ @Override
+ StructTopic getTopic();
+
+ /**
+ * Get the last published value. If no value has been published or the value cannot be unpacked,
+ * returns the stored default value.
+ *
+ * @return value
+ */
+ T get();
+
+ /**
+ * Get the last published value. If no value has been published or the value cannot be unpacked,
+ * returns the passed defaultValue.
+ *
+ * @param defaultValue default value to return if no value has been published
+ * @return value
+ */
+ T get(T defaultValue);
+
+ /**
+ * Get the last published value, replacing the contents in place of an existing object. If no
+ * value has been published or the value cannot be unpacked, does not replace the contents and
+ * returns false. This function will not work (will throw UnsupportedOperationException) unless T
+ * is mutable (and the implementation of Struct implements unpackInto).
+ *
+ * Note: due to Java language limitations, it's not possible to validate at compile time that
+ * the out parameter is mutable.
+ *
+ * @param out object to replace contents of; must be mutable
+ * @return true if successful, false if no value has been published
+ * @throws UnsupportedOperationException if T is immutable
+ */
+ boolean getInto(T out);
+
+ /**
+ * Get the last published value along with its timestamp. If no value has been published or the
+ * value cannot be unpacked, returns the stored default value and a timestamp of 0.
+ *
+ * @return timestamped value
+ */
+ TimestampedObject getAtomic();
+
+ /**
+ * Get the last published value along with its timestamp If no value has been published or the
+ * value cannot be unpacked, returns the passed defaultValue and a timestamp of 0.
+ *
+ * @param defaultValue default value to return if no value has been published
+ * @return timestamped value
+ */
+ TimestampedObject getAtomic(T defaultValue);
+
+ /**
+ * Get an array of all valid value changes since the last call to readQueue. Also provides a
+ * timestamp for each value. Values that cannot be unpacked are dropped.
+ *
+ * The "poll storage" subscribe option can be used to set the queue depth.
+ *
+ * @return Array of timestamped values; empty array if no valid new changes have been published
+ * since the previous call.
+ */
+ TimestampedObject[] readQueue();
+
+ /**
+ * Get an array of all value changes since the last call to readQueue. Values that cannot be
+ * unpacked are dropped.
+ *
+ *