001// Copyright (c) FIRST and other WPILib contributors.
002// Open Source Software; you can modify and/or share it under the terms of
003// the WPILib BSD license file in the root directory of this project.
004
005package edu.wpi.first.epilogue.logging;
006
007import edu.wpi.first.networktables.BooleanArrayPublisher;
008import edu.wpi.first.networktables.BooleanPublisher;
009import edu.wpi.first.networktables.DoubleArrayPublisher;
010import edu.wpi.first.networktables.DoublePublisher;
011import edu.wpi.first.networktables.FloatArrayPublisher;
012import edu.wpi.first.networktables.FloatPublisher;
013import edu.wpi.first.networktables.IntegerArrayPublisher;
014import edu.wpi.first.networktables.IntegerPublisher;
015import edu.wpi.first.networktables.NetworkTableInstance;
016import edu.wpi.first.networktables.ProtobufPublisher;
017import edu.wpi.first.networktables.Publisher;
018import edu.wpi.first.networktables.RawPublisher;
019import edu.wpi.first.networktables.StringArrayPublisher;
020import edu.wpi.first.networktables.StringPublisher;
021import edu.wpi.first.networktables.StructArrayPublisher;
022import edu.wpi.first.networktables.StructPublisher;
023import edu.wpi.first.util.protobuf.Protobuf;
024import edu.wpi.first.util.struct.Struct;
025import java.util.HashMap;
026import java.util.HashSet;
027import java.util.Map;
028import java.util.Set;
029import java.util.function.Function;
030import us.hebi.quickbuf.ProtoMessage;
031
032/**
033 * A backend implementation that sends data over network tables. Be careful when using this, since
034 * sending too much data may cause bandwidth or CPU starvation.
035 */
036public class NTEpilogueBackend implements EpilogueBackend {
037  private final NetworkTableInstance m_nt;
038
039  private final Map<String, Publisher> m_publishers = new HashMap<>();
040  private final Map<String, NestedBackend> m_nestedBackends = new HashMap<>();
041  private final Set<Struct<?>> m_seenSchemas = new HashSet<>();
042  private final Set<Protobuf<?, ?>> m_seenProtos = new HashSet<>();
043  private final Function<String, IntegerPublisher> m_createIntPublisher;
044  private final Function<String, FloatPublisher> m_createFloatPublisher;
045  private final Function<String, DoublePublisher> m_createDoublePublisher;
046  private final Function<String, BooleanPublisher> m_createBooleanPublisher;
047  private final Function<String, RawPublisher> m_createRawPublisher;
048  private final Function<String, IntegerArrayPublisher> m_createIntegerArrayPublisher;
049  private final Function<String, FloatArrayPublisher> m_createFloatArrayPublisher;
050  private final Function<String, DoubleArrayPublisher> m_createDoubleArrayPublisher;
051  private final Function<String, BooleanArrayPublisher> m_createBooleanArrayPublisher;
052  private final Function<String, StringPublisher> m_createStringPublisher;
053  private final Function<String, StringArrayPublisher> m_createStringArrayPublisher;
054
055  /**
056   * Creates a logging backend that sends information to NetworkTables.
057   *
058   * @param nt the NetworkTable instance to use to send data to
059   */
060  @SuppressWarnings("unchecked")
061  public NTEpilogueBackend(NetworkTableInstance nt) {
062    this.m_nt = nt;
063    m_createIntPublisher = identifier -> m_nt.getIntegerTopic(identifier).publish();
064    m_createFloatPublisher = identifier -> m_nt.getFloatTopic(identifier).publish();
065    m_createDoublePublisher = identifier -> m_nt.getDoubleTopic(identifier).publish();
066    m_createBooleanPublisher = identifier -> m_nt.getBooleanTopic(identifier).publish();
067    m_createRawPublisher = identifier -> m_nt.getRawTopic(identifier).publish("raw");
068    m_createIntegerArrayPublisher = identifier -> m_nt.getIntegerArrayTopic(identifier).publish();
069    m_createFloatArrayPublisher = identifier -> m_nt.getFloatArrayTopic(identifier).publish();
070    m_createDoubleArrayPublisher = identifier -> m_nt.getDoubleArrayTopic(identifier).publish();
071    m_createBooleanArrayPublisher = identifier -> m_nt.getBooleanArrayTopic(identifier).publish();
072    m_createStringPublisher = identifier -> m_nt.getStringTopic(identifier).publish();
073    m_createStringArrayPublisher = identifier -> m_nt.getStringArrayTopic(identifier).publish();
074  }
075
076  @Override
077  public EpilogueBackend getNested(String path) {
078    if (!m_nestedBackends.containsKey(path)) {
079      var nested = new NestedBackend(path, this);
080      m_nestedBackends.put(path, nested);
081      return nested;
082    }
083
084    return m_nestedBackends.get(path);
085  }
086
087  @Override
088  public void log(String identifier, int value) {
089    ((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value);
090  }
091
092  @Override
093  public void log(String identifier, long value) {
094    ((IntegerPublisher) m_publishers.computeIfAbsent(identifier, m_createIntPublisher)).set(value);
095  }
096
097  @Override
098  public void log(String identifier, float value) {
099    ((FloatPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatPublisher)).set(value);
100  }
101
102  @Override
103  public void log(String identifier, double value) {
104    ((DoublePublisher) m_publishers.computeIfAbsent(identifier, m_createDoublePublisher))
105        .set(value);
106  }
107
108  @Override
109  public void log(String identifier, boolean value) {
110    ((BooleanPublisher) m_publishers.computeIfAbsent(identifier, m_createBooleanPublisher))
111        .set(value);
112  }
113
114  @Override
115  public void log(String identifier, byte[] value) {
116    ((RawPublisher) m_publishers.computeIfAbsent(identifier, m_createRawPublisher)).set(value);
117  }
118
119  @Override
120  @SuppressWarnings("PMD.UnnecessaryCastRule")
121  public void log(String identifier, int[] value) {
122    // NT backend only supports int64[], so we have to manually widen to 64 bits before sending
123    long[] widened = new long[value.length];
124
125    for (int i = 0; i < value.length; i++) {
126      widened[i] = (long) value[i];
127    }
128
129    ((IntegerArrayPublisher)
130            m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher))
131        .set(widened);
132  }
133
134  @Override
135  public void log(String identifier, long[] value) {
136    ((IntegerArrayPublisher)
137            m_publishers.computeIfAbsent(identifier, m_createIntegerArrayPublisher))
138        .set(value);
139  }
140
141  @Override
142  public void log(String identifier, float[] value) {
143    ((FloatArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createFloatArrayPublisher))
144        .set(value);
145  }
146
147  @Override
148  public void log(String identifier, double[] value) {
149    ((DoubleArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createDoubleArrayPublisher))
150        .set(value);
151  }
152
153  @Override
154  public void log(String identifier, boolean[] value) {
155    ((BooleanArrayPublisher)
156            m_publishers.computeIfAbsent(identifier, m_createBooleanArrayPublisher))
157        .set(value);
158  }
159
160  @Override
161  public void log(String identifier, String value) {
162    ((StringPublisher) m_publishers.computeIfAbsent(identifier, m_createStringPublisher))
163        .set(value);
164  }
165
166  @Override
167  public void log(String identifier, String[] value) {
168    ((StringArrayPublisher) m_publishers.computeIfAbsent(identifier, m_createStringArrayPublisher))
169        .set(value);
170  }
171
172  @Override
173  @SuppressWarnings("unchecked")
174  public <S> void log(String identifier, S value, Struct<S> struct) {
175    // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
176    if (m_seenSchemas.add(struct)) {
177      m_nt.addSchema(struct);
178    }
179
180    if (m_publishers.containsKey(identifier)) {
181      ((StructPublisher<S>) m_publishers.get(identifier)).set(value);
182    } else {
183      StructPublisher<S> publisher = m_nt.getStructTopic(identifier, struct).publish();
184      m_publishers.put(identifier, publisher);
185      publisher.set(value);
186    }
187  }
188
189  @Override
190  @SuppressWarnings("unchecked")
191  public <S> void log(String identifier, S[] value, Struct<S> struct) {
192    // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
193    if (m_seenSchemas.add(struct)) {
194      m_nt.addSchema(struct);
195    }
196
197    if (m_publishers.containsKey(identifier)) {
198      ((StructArrayPublisher<S>) m_publishers.get(identifier)).set(value);
199    } else {
200      StructArrayPublisher<S> publisher = m_nt.getStructArrayTopic(identifier, struct).publish();
201      m_publishers.put(identifier, publisher);
202      publisher.set(value);
203    }
204  }
205
206  @Override
207  @SuppressWarnings("unchecked")
208  public <P, M extends ProtoMessage<M>> void log(String identifier, P value, Protobuf<P, M> proto) {
209    // NetworkTableInstance.addSchema has checks that we're able to skip, avoiding allocations
210    if (m_seenProtos.add(proto)) {
211      m_nt.addSchema(proto);
212    }
213
214    if (m_publishers.containsKey(identifier)) {
215      ((ProtobufPublisher<P>) m_publishers.get(identifier)).set(value);
216    } else {
217      ProtobufPublisher<P> publisher = m_nt.getProtobufTopic(identifier, proto).publish();
218      m_publishers.put(identifier, publisher);
219      publisher.set(value);
220    }
221  }
222}