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 005// THIS FILE WAS AUTO-GENERATED BY ./ntcore/generate_topics.py. DO NOT MODIFY 006 007package edu.wpi.first.networktables; 008 009import edu.wpi.first.util.WPIUtilJNI; 010import edu.wpi.first.util.concurrent.Event; 011import edu.wpi.first.util.datalog.DataLog; 012import edu.wpi.first.util.protobuf.Protobuf; 013import edu.wpi.first.util.struct.Struct; 014import java.nio.charset.StandardCharsets; 015import java.util.EnumSet; 016import java.util.HashMap; 017import java.util.HashSet; 018import java.util.Map; 019import java.util.OptionalLong; 020import java.util.Set; 021import java.util.concurrent.ConcurrentHashMap; 022import java.util.concurrent.ConcurrentMap; 023import java.util.concurrent.TimeUnit; 024import java.util.concurrent.locks.Condition; 025import java.util.concurrent.locks.ReentrantLock; 026import java.util.function.Consumer; 027import us.hebi.quickbuf.ProtoMessage; 028 029/** 030 * NetworkTables Instance. 031 * 032 * <p>Instances are completely independent from each other. Table operations on one instance will 033 * not be visible to other instances unless the instances are connected via the network. The main 034 * limitation on instances is that you cannot have two servers on the same network port. The main 035 * utility of instances is for unit testing, but they can also enable one program to connect to two 036 * different NetworkTables networks. 037 * 038 * <p>The global "default" instance (as returned by {@link #getDefault()}) is always available, and 039 * is intended for the common case when there is only a single NetworkTables instance being used in 040 * the program. 041 * 042 * <p>Additional instances can be created with the {@link #create()} function. A reference must be 043 * kept to the NetworkTableInstance returned by this function to keep it from being garbage 044 * collected. 045 */ 046@SuppressWarnings("PMD.CouplingBetweenObjects") 047public final class NetworkTableInstance implements AutoCloseable { 048 /** Client/server mode flag values (as returned by {@link #getNetworkMode()}). */ 049 public enum NetworkMode { 050 /** Running in server mode. */ 051 kServer(0x01), 052 053 /** Running in NT3 client mode. */ 054 kClient3(0x02), 055 056 /** Running in NT4 client mode. */ 057 kClient4(0x04), 058 059 /** Currently starting up (either client or server). */ 060 kStarting(0x08), 061 062 /** Running in local-only mode. */ 063 kLocal(0x10); 064 065 private final int value; 066 067 NetworkMode(int value) { 068 this.value = value; 069 } 070 071 public int getValue() { 072 return value; 073 } 074 } 075 076 /** The default port that network tables operates on for NT3. */ 077 public static final int kDefaultPort3 = 1735; 078 079 /** The default port that network tables operates on for NT4. */ 080 public static final int kDefaultPort4 = 5810; 081 082 /** 083 * Construct from native handle. 084 * 085 * @param handle Native handle 086 */ 087 private NetworkTableInstance(int handle) { 088 m_owned = false; 089 m_handle = handle; 090 } 091 092 /** Destroys the instance (if created by {@link #create()}). */ 093 @Override 094 public synchronized void close() { 095 if (m_owned && m_handle != 0) { 096 m_listeners.close(); 097 m_schemas.forEach((k, v) -> v.close()); 098 NetworkTablesJNI.destroyInstance(m_handle); 099 m_handle = 0; 100 } 101 } 102 103 /** 104 * Determines if the native handle is valid. 105 * 106 * @return True if the native handle is valid, false otherwise. 107 */ 108 public boolean isValid() { 109 return m_handle != 0; 110 } 111 112 /* The default instance. */ 113 private static NetworkTableInstance s_defaultInstance; 114 115 /** 116 * Get global default instance. 117 * 118 * @return Global default instance 119 */ 120 public static synchronized NetworkTableInstance getDefault() { 121 if (s_defaultInstance == null) { 122 s_defaultInstance = new NetworkTableInstance(NetworkTablesJNI.getDefaultInstance()); 123 } 124 return s_defaultInstance; 125 } 126 127 /** 128 * Create an instance. Note: A reference to the returned instance must be retained to ensure the 129 * instance is not garbage collected. 130 * 131 * @return Newly created instance 132 */ 133 public static NetworkTableInstance create() { 134 NetworkTableInstance inst = new NetworkTableInstance(NetworkTablesJNI.createInstance()); 135 inst.m_owned = true; 136 return inst; 137 } 138 139 /** 140 * Gets the native handle for the instance. 141 * 142 * @return Native handle 143 */ 144 public int getHandle() { 145 return m_handle; 146 } 147 148 /** 149 * Get (generic) topic. 150 * 151 * @param name topic name 152 * @return Topic 153 */ 154 public Topic getTopic(String name) { 155 Topic topic = m_topics.get(name); 156 if (topic == null) { 157 int handle = NetworkTablesJNI.getTopic(m_handle, name); 158 topic = new Topic(this, handle); 159 Topic oldTopic = m_topics.putIfAbsent(name, topic); 160 if (oldTopic != null) { 161 topic = oldTopic; 162 } 163 // also cache by handle 164 m_topicsByHandle.putIfAbsent(handle, topic); 165 } 166 return topic; 167 } 168 169 /** 170 * Get boolean topic. 171 * 172 * @param name topic name 173 * @return BooleanTopic 174 */ 175 public BooleanTopic getBooleanTopic(String name) { 176 Topic topic = m_topics.get(name); 177 if (topic instanceof BooleanTopic) { 178 return (BooleanTopic) topic; 179 } 180 181 int handle; 182 if (topic == null) { 183 handle = NetworkTablesJNI.getTopic(m_handle, name); 184 } else { 185 handle = topic.getHandle(); 186 } 187 188 BooleanTopic wrapTopic = new BooleanTopic(this, handle); 189 m_topics.put(name, wrapTopic); 190 191 // also cache by handle 192 m_topicsByHandle.put(handle, wrapTopic); 193 194 return wrapTopic; 195 } 196 197 /** 198 * Get long topic. 199 * 200 * @param name topic name 201 * @return IntegerTopic 202 */ 203 public IntegerTopic getIntegerTopic(String name) { 204 Topic topic = m_topics.get(name); 205 if (topic instanceof IntegerTopic) { 206 return (IntegerTopic) topic; 207 } 208 209 int handle; 210 if (topic == null) { 211 handle = NetworkTablesJNI.getTopic(m_handle, name); 212 } else { 213 handle = topic.getHandle(); 214 } 215 216 IntegerTopic wrapTopic = new IntegerTopic(this, handle); 217 m_topics.put(name, wrapTopic); 218 219 // also cache by handle 220 m_topicsByHandle.put(handle, wrapTopic); 221 222 return wrapTopic; 223 } 224 225 /** 226 * Get float topic. 227 * 228 * @param name topic name 229 * @return FloatTopic 230 */ 231 public FloatTopic getFloatTopic(String name) { 232 Topic topic = m_topics.get(name); 233 if (topic instanceof FloatTopic) { 234 return (FloatTopic) topic; 235 } 236 237 int handle; 238 if (topic == null) { 239 handle = NetworkTablesJNI.getTopic(m_handle, name); 240 } else { 241 handle = topic.getHandle(); 242 } 243 244 FloatTopic wrapTopic = new FloatTopic(this, handle); 245 m_topics.put(name, wrapTopic); 246 247 // also cache by handle 248 m_topicsByHandle.put(handle, wrapTopic); 249 250 return wrapTopic; 251 } 252 253 /** 254 * Get double topic. 255 * 256 * @param name topic name 257 * @return DoubleTopic 258 */ 259 public DoubleTopic getDoubleTopic(String name) { 260 Topic topic = m_topics.get(name); 261 if (topic instanceof DoubleTopic) { 262 return (DoubleTopic) topic; 263 } 264 265 int handle; 266 if (topic == null) { 267 handle = NetworkTablesJNI.getTopic(m_handle, name); 268 } else { 269 handle = topic.getHandle(); 270 } 271 272 DoubleTopic wrapTopic = new DoubleTopic(this, handle); 273 m_topics.put(name, wrapTopic); 274 275 // also cache by handle 276 m_topicsByHandle.put(handle, wrapTopic); 277 278 return wrapTopic; 279 } 280 281 /** 282 * Get String topic. 283 * 284 * @param name topic name 285 * @return StringTopic 286 */ 287 public StringTopic getStringTopic(String name) { 288 Topic topic = m_topics.get(name); 289 if (topic instanceof StringTopic) { 290 return (StringTopic) topic; 291 } 292 293 int handle; 294 if (topic == null) { 295 handle = NetworkTablesJNI.getTopic(m_handle, name); 296 } else { 297 handle = topic.getHandle(); 298 } 299 300 StringTopic wrapTopic = new StringTopic(this, handle); 301 m_topics.put(name, wrapTopic); 302 303 // also cache by handle 304 m_topicsByHandle.put(handle, wrapTopic); 305 306 return wrapTopic; 307 } 308 309 /** 310 * Get byte[] topic. 311 * 312 * @param name topic name 313 * @return RawTopic 314 */ 315 public RawTopic getRawTopic(String name) { 316 Topic topic = m_topics.get(name); 317 if (topic instanceof RawTopic) { 318 return (RawTopic) topic; 319 } 320 321 int handle; 322 if (topic == null) { 323 handle = NetworkTablesJNI.getTopic(m_handle, name); 324 } else { 325 handle = topic.getHandle(); 326 } 327 328 RawTopic wrapTopic = new RawTopic(this, handle); 329 m_topics.put(name, wrapTopic); 330 331 // also cache by handle 332 m_topicsByHandle.put(handle, wrapTopic); 333 334 return wrapTopic; 335 } 336 337 /** 338 * Get boolean[] topic. 339 * 340 * @param name topic name 341 * @return BooleanArrayTopic 342 */ 343 public BooleanArrayTopic getBooleanArrayTopic(String name) { 344 Topic topic = m_topics.get(name); 345 if (topic instanceof BooleanArrayTopic) { 346 return (BooleanArrayTopic) topic; 347 } 348 349 int handle; 350 if (topic == null) { 351 handle = NetworkTablesJNI.getTopic(m_handle, name); 352 } else { 353 handle = topic.getHandle(); 354 } 355 356 BooleanArrayTopic wrapTopic = new BooleanArrayTopic(this, handle); 357 m_topics.put(name, wrapTopic); 358 359 // also cache by handle 360 m_topicsByHandle.put(handle, wrapTopic); 361 362 return wrapTopic; 363 } 364 365 /** 366 * Get long[] topic. 367 * 368 * @param name topic name 369 * @return IntegerArrayTopic 370 */ 371 public IntegerArrayTopic getIntegerArrayTopic(String name) { 372 Topic topic = m_topics.get(name); 373 if (topic instanceof IntegerArrayTopic) { 374 return (IntegerArrayTopic) topic; 375 } 376 377 int handle; 378 if (topic == null) { 379 handle = NetworkTablesJNI.getTopic(m_handle, name); 380 } else { 381 handle = topic.getHandle(); 382 } 383 384 IntegerArrayTopic wrapTopic = new IntegerArrayTopic(this, handle); 385 m_topics.put(name, wrapTopic); 386 387 // also cache by handle 388 m_topicsByHandle.put(handle, wrapTopic); 389 390 return wrapTopic; 391 } 392 393 /** 394 * Get float[] topic. 395 * 396 * @param name topic name 397 * @return FloatArrayTopic 398 */ 399 public FloatArrayTopic getFloatArrayTopic(String name) { 400 Topic topic = m_topics.get(name); 401 if (topic instanceof FloatArrayTopic) { 402 return (FloatArrayTopic) topic; 403 } 404 405 int handle; 406 if (topic == null) { 407 handle = NetworkTablesJNI.getTopic(m_handle, name); 408 } else { 409 handle = topic.getHandle(); 410 } 411 412 FloatArrayTopic wrapTopic = new FloatArrayTopic(this, handle); 413 m_topics.put(name, wrapTopic); 414 415 // also cache by handle 416 m_topicsByHandle.put(handle, wrapTopic); 417 418 return wrapTopic; 419 } 420 421 /** 422 * Get double[] topic. 423 * 424 * @param name topic name 425 * @return DoubleArrayTopic 426 */ 427 public DoubleArrayTopic getDoubleArrayTopic(String name) { 428 Topic topic = m_topics.get(name); 429 if (topic instanceof DoubleArrayTopic) { 430 return (DoubleArrayTopic) topic; 431 } 432 433 int handle; 434 if (topic == null) { 435 handle = NetworkTablesJNI.getTopic(m_handle, name); 436 } else { 437 handle = topic.getHandle(); 438 } 439 440 DoubleArrayTopic wrapTopic = new DoubleArrayTopic(this, handle); 441 m_topics.put(name, wrapTopic); 442 443 // also cache by handle 444 m_topicsByHandle.put(handle, wrapTopic); 445 446 return wrapTopic; 447 } 448 449 /** 450 * Get String[] topic. 451 * 452 * @param name topic name 453 * @return StringArrayTopic 454 */ 455 public StringArrayTopic getStringArrayTopic(String name) { 456 Topic topic = m_topics.get(name); 457 if (topic instanceof StringArrayTopic) { 458 return (StringArrayTopic) topic; 459 } 460 461 int handle; 462 if (topic == null) { 463 handle = NetworkTablesJNI.getTopic(m_handle, name); 464 } else { 465 handle = topic.getHandle(); 466 } 467 468 StringArrayTopic wrapTopic = new StringArrayTopic(this, handle); 469 m_topics.put(name, wrapTopic); 470 471 // also cache by handle 472 m_topicsByHandle.put(handle, wrapTopic); 473 474 return wrapTopic; 475 } 476 477 478 /** 479 * Get protobuf-encoded value topic. 480 * 481 * @param <T> value class (inferred from proto) 482 * @param <MessageType> protobuf message type (inferred from proto) 483 * @param name topic name 484 * @param proto protobuf serialization implementation 485 * @return ProtobufTopic 486 */ 487 public <T, MessageType extends ProtoMessage<?>> 488 ProtobufTopic<T> getProtobufTopic(String name, Protobuf<T, MessageType> proto) { 489 Topic topic = m_topics.get(name); 490 if (topic instanceof ProtobufTopic<?> 491 && ((ProtobufTopic<?>) topic).getProto().equals(proto)) { 492 @SuppressWarnings("unchecked") 493 ProtobufTopic<T> wrapTopic = (ProtobufTopic<T>) topic; 494 return wrapTopic; 495 } 496 497 int handle; 498 if (topic == null) { 499 handle = NetworkTablesJNI.getTopic(m_handle, name); 500 } else { 501 handle = topic.getHandle(); 502 } 503 504 ProtobufTopic<T> wrapTopic = ProtobufTopic.wrap(this, handle, proto); 505 m_topics.put(name, wrapTopic); 506 507 // also cache by handle 508 m_topicsByHandle.put(handle, wrapTopic); 509 510 return wrapTopic; 511 } 512 513 /** 514 * Get struct-encoded value topic. 515 * 516 * @param <T> value class (inferred from struct) 517 * @param name topic name 518 * @param struct struct serialization implementation 519 * @return StructTopic 520 */ 521 public <T> 522 StructTopic<T> getStructTopic(String name, Struct<T> struct) { 523 Topic topic = m_topics.get(name); 524 if (topic instanceof StructTopic<?> 525 && ((StructTopic<?>) topic).getStruct().equals(struct)) { 526 @SuppressWarnings("unchecked") 527 StructTopic<T> wrapTopic = (StructTopic<T>) topic; 528 return wrapTopic; 529 } 530 531 int handle; 532 if (topic == null) { 533 handle = NetworkTablesJNI.getTopic(m_handle, name); 534 } else { 535 handle = topic.getHandle(); 536 } 537 538 StructTopic<T> wrapTopic = StructTopic.wrap(this, handle, struct); 539 m_topics.put(name, wrapTopic); 540 541 // also cache by handle 542 m_topicsByHandle.put(handle, wrapTopic); 543 544 return wrapTopic; 545 } 546 547 /** 548 * Get struct-encoded value array topic. 549 * 550 * @param <T> value class (inferred from struct) 551 * @param name topic name 552 * @param struct struct serialization implementation 553 * @return StructArrayTopic 554 */ 555 public <T> 556 StructArrayTopic<T> getStructArrayTopic(String name, Struct<T> struct) { 557 Topic topic = m_topics.get(name); 558 if (topic instanceof StructArrayTopic<?> 559 && ((StructArrayTopic<?>) topic).getStruct().equals(struct)) { 560 @SuppressWarnings("unchecked") 561 StructArrayTopic<T> wrapTopic = (StructArrayTopic<T>) topic; 562 return wrapTopic; 563 } 564 565 int handle; 566 if (topic == null) { 567 handle = NetworkTablesJNI.getTopic(m_handle, name); 568 } else { 569 handle = topic.getHandle(); 570 } 571 572 StructArrayTopic<T> wrapTopic = StructArrayTopic.wrap(this, handle, struct); 573 m_topics.put(name, wrapTopic); 574 575 // also cache by handle 576 m_topicsByHandle.put(handle, wrapTopic); 577 578 return wrapTopic; 579 } 580 581 private Topic[] topicHandlesToTopics(int[] handles) { 582 Topic[] topics = new Topic[handles.length]; 583 for (int i = 0; i < handles.length; i++) { 584 topics[i] = getCachedTopic(handles[i]); 585 } 586 return topics; 587 } 588 589 /** 590 * Get all published topics. 591 * 592 * @return Array of topics. 593 */ 594 public Topic[] getTopics() { 595 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, "", 0)); 596 } 597 598 /** 599 * Get published topics starting with the given prefix. The results are optionally filtered by 600 * string prefix to only return a subset of all topics. 601 * 602 * @param prefix topic name required prefix; only topics whose name starts with this string are 603 * returned 604 * @return Array of topic information. 605 */ 606 public Topic[] getTopics(String prefix) { 607 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, prefix, 0)); 608 } 609 610 /** 611 * Get published topics starting with the given prefix. The results are optionally filtered by 612 * string prefix and data type to only return a subset of all topics. 613 * 614 * @param prefix topic name required prefix; only topics whose name starts with this string are 615 * returned 616 * @param types bitmask of data types; 0 is treated as a "don't care" 617 * @return Array of topic information. 618 */ 619 public Topic[] getTopics(String prefix, int types) { 620 return topicHandlesToTopics(NetworkTablesJNI.getTopics(m_handle, prefix, types)); 621 } 622 623 /** 624 * Get published topics starting with the given prefix. The results are optionally filtered by 625 * string prefix and data type to only return a subset of all topics. 626 * 627 * @param prefix topic name required prefix; only topics whose name starts with this string are 628 * returned 629 * @param types array of data type strings 630 * @return Array of topic information. 631 */ 632 public Topic[] getTopics(String prefix, String[] types) { 633 return topicHandlesToTopics(NetworkTablesJNI.getTopicsStr(m_handle, prefix, types)); 634 } 635 636 /** 637 * Get information about all topics. 638 * 639 * @return Array of topic information. 640 */ 641 public TopicInfo[] getTopicInfo() { 642 return NetworkTablesJNI.getTopicInfos(this, m_handle, "", 0); 643 } 644 645 /** 646 * Get information about topics starting with the given prefix. The results are optionally 647 * filtered by string prefix to only return a subset of all topics. 648 * 649 * @param prefix topic name required prefix; only topics whose name starts with this string are 650 * returned 651 * @return Array of topic information. 652 */ 653 public TopicInfo[] getTopicInfo(String prefix) { 654 return NetworkTablesJNI.getTopicInfos(this, m_handle, prefix, 0); 655 } 656 657 /** 658 * Get information about topics starting with the given prefix. The results are optionally 659 * filtered by string prefix and data type to only return a subset of all topics. 660 * 661 * @param prefix topic name required prefix; only topics whose name starts with this string are 662 * returned 663 * @param types bitmask of data types; 0 is treated as a "don't care" 664 * @return Array of topic information. 665 */ 666 public TopicInfo[] getTopicInfo(String prefix, int types) { 667 return NetworkTablesJNI.getTopicInfos(this, m_handle, prefix, types); 668 } 669 670 /** 671 * Get information about topics starting with the given prefix. The results are optionally 672 * filtered by string prefix and data type to only return a subset of all topics. 673 * 674 * @param prefix topic name required prefix; only topics whose name starts with this string are 675 * returned 676 * @param types array of data type strings 677 * @return Array of topic information. 678 */ 679 public TopicInfo[] getTopicInfo(String prefix, String[] types) { 680 return NetworkTablesJNI.getTopicInfosStr(this, m_handle, prefix, types); 681 } 682 683 /* Cache of created entries. */ 684 private final ConcurrentMap<String, NetworkTableEntry> m_entries = new ConcurrentHashMap<>(); 685 686 /** 687 * Gets the entry for a key. 688 * 689 * @param name Key 690 * @return Network table entry. 691 */ 692 public NetworkTableEntry getEntry(String name) { 693 NetworkTableEntry entry = m_entries.get(name); 694 if (entry == null) { 695 entry = new NetworkTableEntry(this, NetworkTablesJNI.getEntry(m_handle, name)); 696 NetworkTableEntry oldEntry = m_entries.putIfAbsent(name, entry); 697 if (oldEntry != null) { 698 entry = oldEntry; 699 } 700 } 701 return entry; 702 } 703 704 /* Cache of created topics. */ 705 private final ConcurrentMap<String, Topic> m_topics = new ConcurrentHashMap<>(); 706 private final ConcurrentMap<Integer, Topic> m_topicsByHandle = new ConcurrentHashMap<>(); 707 708 Topic getCachedTopic(String name) { 709 Topic topic = m_topics.get(name); 710 if (topic == null) { 711 int handle = NetworkTablesJNI.getTopic(m_handle, name); 712 topic = new Topic(this, handle); 713 Topic oldTopic = m_topics.putIfAbsent(name, topic); 714 if (oldTopic != null) { 715 topic = oldTopic; 716 } 717 // also cache by handle 718 m_topicsByHandle.putIfAbsent(handle, topic); 719 } 720 return topic; 721 } 722 723 Topic getCachedTopic(int handle) { 724 Topic topic = m_topicsByHandle.get(handle); 725 if (topic == null) { 726 topic = new Topic(this, handle); 727 Topic oldTopic = m_topicsByHandle.putIfAbsent(handle, topic); 728 if (oldTopic != null) { 729 topic = oldTopic; 730 } 731 } 732 return topic; 733 } 734 735 /* Cache of created tables. */ 736 private final ConcurrentMap<String, NetworkTable> m_tables = new ConcurrentHashMap<>(); 737 738 /** 739 * Gets the table with the specified key. 740 * 741 * @param key the key name 742 * @return The network table 743 */ 744 public NetworkTable getTable(String key) { 745 // prepend leading / if not present 746 String theKey; 747 if (key.isEmpty() || "/".equals(key)) { 748 theKey = ""; 749 } else if (key.charAt(0) == NetworkTable.PATH_SEPARATOR) { 750 theKey = key; 751 } else { 752 theKey = NetworkTable.PATH_SEPARATOR + key; 753 } 754 755 // cache created tables 756 NetworkTable table = m_tables.get(theKey); 757 if (table == null) { 758 table = new NetworkTable(this, theKey); 759 NetworkTable oldTable = m_tables.putIfAbsent(theKey, table); 760 if (oldTable != null) { 761 table = oldTable; 762 } 763 } 764 return table; 765 } 766 767 /* 768 * Callback Creation Functions 769 */ 770 771 private static class ListenerStorage implements AutoCloseable { 772 private final ReentrantLock m_lock = new ReentrantLock(); 773 private final Map<Integer, Consumer<NetworkTableEvent>> m_listeners = new HashMap<>(); 774 private Thread m_thread; 775 private int m_poller; 776 private boolean m_waitQueue; 777 private final Event m_waitQueueEvent = new Event(); 778 private final Condition m_waitQueueCond = m_lock.newCondition(); 779 private final NetworkTableInstance m_inst; 780 781 ListenerStorage(NetworkTableInstance inst) { 782 m_inst = inst; 783 } 784 785 int add( 786 String[] prefixes, 787 EnumSet<NetworkTableEvent.Kind> eventKinds, 788 Consumer<NetworkTableEvent> listener) { 789 m_lock.lock(); 790 try { 791 if (m_poller == 0) { 792 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 793 startThread(); 794 } 795 int h = NetworkTablesJNI.addListener(m_poller, prefixes, eventKinds); 796 m_listeners.put(h, listener); 797 return h; 798 } finally { 799 m_lock.unlock(); 800 } 801 } 802 803 int add( 804 int handle, 805 EnumSet<NetworkTableEvent.Kind> eventKinds, 806 Consumer<NetworkTableEvent> listener) { 807 m_lock.lock(); 808 try { 809 if (m_poller == 0) { 810 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 811 startThread(); 812 } 813 int h = NetworkTablesJNI.addListener(m_poller, handle, eventKinds); 814 m_listeners.put(h, listener); 815 return h; 816 } finally { 817 m_lock.unlock(); 818 } 819 } 820 821 int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> listener) { 822 m_lock.lock(); 823 try { 824 if (m_poller == 0) { 825 m_poller = NetworkTablesJNI.createListenerPoller(m_inst.getHandle()); 826 startThread(); 827 } 828 int h = NetworkTablesJNI.addLogger(m_poller, minLevel, maxLevel); 829 m_listeners.put(h, listener); 830 return h; 831 } finally { 832 m_lock.unlock(); 833 } 834 } 835 836 void remove(int listener) { 837 m_lock.lock(); 838 try { 839 m_listeners.remove(listener); 840 } finally { 841 m_lock.unlock(); 842 } 843 NetworkTablesJNI.removeListener(listener); 844 } 845 846 @Override 847 public void close() { 848 if (m_poller != 0) { 849 NetworkTablesJNI.destroyListenerPoller(m_poller); 850 } 851 m_poller = 0; 852 } 853 854 private void startThread() { 855 m_thread = 856 new Thread( 857 () -> { 858 boolean wasInterrupted = false; 859 int[] handles = new int[] { m_poller, m_waitQueueEvent.getHandle() }; 860 while (!Thread.interrupted()) { 861 try { 862 WPIUtilJNI.waitForObjects(handles); 863 } catch (InterruptedException ex) { 864 m_lock.lock(); 865 try { 866 if (m_waitQueue) { 867 m_waitQueue = false; 868 m_waitQueueCond.signalAll(); 869 } 870 } finally { 871 m_lock.unlock(); 872 } 873 Thread.currentThread().interrupt(); 874 // don't try to destroy poller, as its handle is likely no longer valid 875 wasInterrupted = true; 876 break; 877 } 878 for (NetworkTableEvent event : 879 NetworkTablesJNI.readListenerQueue(m_inst, m_poller)) { 880 Consumer<NetworkTableEvent> listener; 881 m_lock.lock(); 882 try { 883 listener = m_listeners.get(event.listener); 884 } finally { 885 m_lock.unlock(); 886 } 887 if (listener != null) { 888 try { 889 listener.accept(event); 890 } catch (Throwable throwable) { 891 System.err.println( 892 "Unhandled exception during listener callback: " 893 + throwable.toString()); 894 throwable.printStackTrace(); 895 } 896 } 897 } 898 m_lock.lock(); 899 try { 900 if (m_waitQueue) { 901 m_waitQueue = false; 902 m_waitQueueCond.signalAll(); 903 } 904 } finally { 905 m_lock.unlock(); 906 } 907 } 908 m_lock.lock(); 909 try { 910 if (!wasInterrupted) { 911 NetworkTablesJNI.destroyListenerPoller(m_poller); 912 } 913 m_poller = 0; 914 } finally { 915 m_lock.unlock(); 916 } 917 }, 918 "NTListener"); 919 m_thread.setDaemon(true); 920 m_thread.start(); 921 } 922 923 boolean waitForQueue(double timeout) { 924 m_lock.lock(); 925 try { 926 if (m_poller != 0) { 927 m_waitQueue = true; 928 m_waitQueueEvent.set(); 929 while (m_waitQueue) { 930 try { 931 if (timeout < 0) { 932 m_waitQueueCond.await(); 933 } else { 934 return m_waitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); 935 } 936 } catch (InterruptedException ex) { 937 Thread.currentThread().interrupt(); 938 return true; 939 } 940 } 941 } 942 } finally { 943 m_lock.unlock(); 944 } 945 return true; 946 } 947 } 948 949 private final ListenerStorage m_listeners = new ListenerStorage(this); 950 951 /** 952 * Remove a connection listener. 953 * 954 * @param listener Listener handle to remove 955 */ 956 public void removeListener(int listener) { 957 m_listeners.remove(listener); 958 } 959 960 /** 961 * Wait for the listener queue to be empty. This is primarily useful for deterministic 962 * testing. This blocks until either the listener queue is empty (e.g. there are no 963 * more events that need to be passed along to callbacks or poll queues) or the timeout expires. 964 * 965 * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, or a negative value to 966 * block indefinitely 967 * @return False if timed out, otherwise true. 968 */ 969 public boolean waitForListenerQueue(double timeout) { 970 return m_listeners.waitForQueue(timeout); 971 } 972 973 /** 974 * Add a connection listener. The callback function is called asynchronously on a separate 975 * thread, so it's important to use synchronization or atomics when accessing any shared state 976 * from the callback function. 977 * 978 * @param immediateNotify Notify listener of all existing connections 979 * @param listener Listener to add 980 * @return Listener handle 981 */ 982 public int addConnectionListener( 983 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 984 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kConnection); 985 if (immediateNotify) { 986 eventKinds.add(NetworkTableEvent.Kind.kImmediate); 987 } 988 return m_listeners.add(m_handle, eventKinds, listener); 989 } 990 991 /** 992 * Add a time synchronization listener. The callback function is called asynchronously on a 993 * separate thread, so it's important to use synchronization or atomics when accessing any shared 994 * state from the callback function. 995 * 996 * @param immediateNotify Notify listener of current time synchronization value 997 * @param listener Listener to add 998 * @return Listener handle 999 */ 1000 public int addTimeSyncListener( 1001 boolean immediateNotify, Consumer<NetworkTableEvent> listener) { 1002 EnumSet<NetworkTableEvent.Kind> eventKinds = EnumSet.of(NetworkTableEvent.Kind.kTimeSync); 1003 if (immediateNotify) { 1004 eventKinds.add(NetworkTableEvent.Kind.kImmediate); 1005 } 1006 return m_listeners.add(m_handle, eventKinds, listener); 1007 } 1008 1009 /** 1010 * Add a listener for changes on a particular topic. The callback function is called 1011 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1012 * accessing any shared state from the callback function. 1013 * 1014 * <p>This creates a corresponding internal subscriber with the lifetime of the 1015 * listener. 1016 * 1017 * @param topic Topic 1018 * @param eventKinds set of event kinds to listen to 1019 * @param listener Listener function 1020 * @return Listener handle 1021 */ 1022 public int addListener( 1023 Topic topic, 1024 EnumSet<NetworkTableEvent.Kind> eventKinds, 1025 Consumer<NetworkTableEvent> listener) { 1026 if (topic.getInstance().getHandle() != m_handle) { 1027 throw new IllegalArgumentException("topic is not from this instance"); 1028 } 1029 return m_listeners.add(topic.getHandle(), eventKinds, listener); 1030 } 1031 1032 /** 1033 * Add a listener for changes on a subscriber. The callback function is called 1034 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1035 * accessing any shared state from the callback function. This does NOT keep the subscriber 1036 * active. 1037 * 1038 * @param subscriber Subscriber 1039 * @param eventKinds set of event kinds to listen to 1040 * @param listener Listener function 1041 * @return Listener handle 1042 */ 1043 public int addListener( 1044 Subscriber subscriber, 1045 EnumSet<NetworkTableEvent.Kind> eventKinds, 1046 Consumer<NetworkTableEvent> listener) { 1047 if (subscriber.getTopic().getInstance().getHandle() != m_handle) { 1048 throw new IllegalArgumentException("subscriber is not from this instance"); 1049 } 1050 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 1051 } 1052 1053 /** 1054 * Add a listener for changes on a subscriber. The callback function is called 1055 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1056 * accessing any shared state from the callback function. This does NOT keep the subscriber 1057 * active. 1058 * 1059 * @param subscriber Subscriber 1060 * @param eventKinds set of event kinds to listen to 1061 * @param listener Listener function 1062 * @return Listener handle 1063 */ 1064 public int addListener( 1065 MultiSubscriber subscriber, 1066 EnumSet<NetworkTableEvent.Kind> eventKinds, 1067 Consumer<NetworkTableEvent> listener) { 1068 if (subscriber.getInstance().getHandle() != m_handle) { 1069 throw new IllegalArgumentException("subscriber is not from this instance"); 1070 } 1071 return m_listeners.add(subscriber.getHandle(), eventKinds, listener); 1072 } 1073 1074 /** 1075 * Add a listener for changes on an entry. The callback function is called 1076 * asynchronously on a separate thread, so it's important to use synchronization or atomics when 1077 * accessing any shared state from the callback function. 1078 * 1079 * @param entry Entry 1080 * @param eventKinds set of event kinds to listen to 1081 * @param listener Listener function 1082 * @return Listener handle 1083 */ 1084 public int addListener( 1085 NetworkTableEntry entry, 1086 EnumSet<NetworkTableEvent.Kind> eventKinds, 1087 Consumer<NetworkTableEvent> listener) { 1088 if (entry.getTopic().getInstance().getHandle() != m_handle) { 1089 throw new IllegalArgumentException("entry is not from this instance"); 1090 } 1091 return m_listeners.add(entry.getHandle(), eventKinds, listener); 1092 } 1093 1094 /** 1095 * Add a listener for changes to topics with names that start with any of the given 1096 * prefixes. The callback function is called asynchronously on a separate thread, so it's 1097 * important to use synchronization or atomics when accessing any shared state from the callback 1098 * function. 1099 * 1100 * <p>This creates a corresponding internal subscriber with the lifetime of the 1101 * listener. 1102 * 1103 * @param prefixes Topic name string prefixes 1104 * @param eventKinds set of event kinds to listen to 1105 * @param listener Listener function 1106 * @return Listener handle 1107 */ 1108 public int addListener( 1109 String[] prefixes, 1110 EnumSet<NetworkTableEvent.Kind> eventKinds, 1111 Consumer<NetworkTableEvent> listener) { 1112 return m_listeners.add(prefixes, eventKinds, listener); 1113 } 1114 1115 /* 1116 * Client/Server Functions 1117 */ 1118 1119 /** 1120 * Get the current network mode. 1121 * 1122 * @return Enum set of NetworkMode. 1123 */ 1124 public EnumSet<NetworkMode> getNetworkMode() { 1125 int flags = NetworkTablesJNI.getNetworkMode(m_handle); 1126 EnumSet<NetworkMode> rv = EnumSet.noneOf(NetworkMode.class); 1127 for (NetworkMode mode : NetworkMode.values()) { 1128 if ((flags & mode.getValue()) != 0) { 1129 rv.add(mode); 1130 } 1131 } 1132 return rv; 1133 } 1134 1135 /** 1136 * Starts local-only operation. Prevents calls to startServer or startClient from taking effect. 1137 * Has no effect if startServer or startClient has already been called. 1138 */ 1139 public void startLocal() { 1140 NetworkTablesJNI.startLocal(m_handle); 1141 } 1142 1143 /** 1144 * Stops local-only operation. startServer or startClient can be called after this call to start 1145 * a server or client. 1146 */ 1147 public void stopLocal() { 1148 NetworkTablesJNI.stopLocal(m_handle); 1149 } 1150 1151 /** 1152 * Starts a server using the networktables.json as the persistent file, using the default 1153 * listening address and port. 1154 */ 1155 public void startServer() { 1156 startServer("networktables.json"); 1157 } 1158 1159 /** 1160 * Starts a server using the specified persistent filename, using the default listening address 1161 * and port. 1162 * 1163 * @param persistFilename the name of the persist file to use 1164 */ 1165 public void startServer(String persistFilename) { 1166 startServer(persistFilename, ""); 1167 } 1168 1169 /** 1170 * Starts a server using the specified filename and listening address, using the default port. 1171 * 1172 * @param persistFilename the name of the persist file to use 1173 * @param listenAddress the address to listen on, or empty to listen on any address 1174 */ 1175 public void startServer(String persistFilename, String listenAddress) { 1176 startServer(persistFilename, listenAddress, kDefaultPort3, kDefaultPort4); 1177 } 1178 1179 /** 1180 * Starts a server using the specified filename, listening address, and port. 1181 * 1182 * @param persistFilename the name of the persist file to use 1183 * @param listenAddress the address to listen on, or empty to listen on any address 1184 * @param port3 port to communicate over (NT3) 1185 */ 1186 public void startServer(String persistFilename, String listenAddress, int port3) { 1187 startServer(persistFilename, listenAddress, port3, kDefaultPort4); 1188 } 1189 1190 /** 1191 * Starts a server using the specified filename, listening address, and port. 1192 * 1193 * @param persistFilename the name of the persist file to use 1194 * @param listenAddress the address to listen on, or empty to listen on any address 1195 * @param port3 port to communicate over (NT3) 1196 * @param port4 port to communicate over (NT4) 1197 */ 1198 public void startServer(String persistFilename, String listenAddress, int port3, int port4) { 1199 NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port3, port4); 1200 } 1201 1202 /** Stops the server if it is running. */ 1203 public void stopServer() { 1204 NetworkTablesJNI.stopServer(m_handle); 1205 } 1206 1207 /** 1208 * Starts a NT3 client. Use SetServer or SetServerTeam to set the server name and port. 1209 * 1210 * @param identity network identity to advertise (cannot be empty string) 1211 */ 1212 public void startClient3(String identity) { 1213 NetworkTablesJNI.startClient3(m_handle, identity); 1214 } 1215 1216 /** 1217 * Starts a NT4 client. Use SetServer or SetServerTeam to set the server name and port. 1218 * 1219 * @param identity network identity to advertise (cannot be empty string) 1220 */ 1221 public void startClient4(String identity) { 1222 NetworkTablesJNI.startClient4(m_handle, identity); 1223 } 1224 1225 /** Stops the client if it is running. */ 1226 public void stopClient() { 1227 NetworkTablesJNI.stopClient(m_handle); 1228 } 1229 1230 /** 1231 * Sets server address and port for client (without restarting client). Changes the port to the 1232 * default port. 1233 * 1234 * @param serverName server name 1235 */ 1236 public void setServer(String serverName) { 1237 setServer(serverName, 0); 1238 } 1239 1240 /** 1241 * Sets server address and port for client (without restarting client). 1242 * 1243 * @param serverName server name 1244 * @param port port to communicate over (0=default) 1245 */ 1246 public void setServer(String serverName, int port) { 1247 NetworkTablesJNI.setServer(m_handle, serverName, port); 1248 } 1249 1250 /** 1251 * Sets server addresses and port for client (without restarting client). Changes the port to the 1252 * default port. The client will attempt to connect to each server in round robin fashion. 1253 * 1254 * @param serverNames array of server names 1255 */ 1256 public void setServer(String[] serverNames) { 1257 setServer(serverNames, 0); 1258 } 1259 1260 /** 1261 * Sets server addresses and port for client (without restarting client). The client will attempt 1262 * to connect to each server in round robin fashion. 1263 * 1264 * @param serverNames array of server names 1265 * @param port port to communicate over (0=default) 1266 */ 1267 public void setServer(String[] serverNames, int port) { 1268 int[] ports = new int[serverNames.length]; 1269 for (int i = 0; i < serverNames.length; i++) { 1270 ports[i] = port; 1271 } 1272 setServer(serverNames, ports); 1273 } 1274 1275 /** 1276 * Sets server addresses and ports for client (without restarting client). The client will 1277 * attempt to connect to each server in round robin fashion. 1278 * 1279 * @param serverNames array of server names 1280 * @param ports array of port numbers (0=default) 1281 */ 1282 public void setServer(String[] serverNames, int[] ports) { 1283 NetworkTablesJNI.setServer(m_handle, serverNames, ports); 1284 } 1285 1286 /** 1287 * Sets server addresses and port for client (without restarting client). Changes the port to the 1288 * default port. The client will attempt to connect to each server in round robin fashion. 1289 * 1290 * @param team team number 1291 */ 1292 public void setServerTeam(int team) { 1293 setServerTeam(team, 0); 1294 } 1295 1296 /** 1297 * Sets server addresses and port for client (without restarting client). Connects using commonly 1298 * known robot addresses for the specified team. 1299 * 1300 * @param team team number 1301 * @param port port to communicate over (0=default) 1302 */ 1303 public void setServerTeam(int team, int port) { 1304 NetworkTablesJNI.setServerTeam(m_handle, team, port); 1305 } 1306 1307 /** 1308 * Disconnects the client if it's running and connected. This will automatically start 1309 * reconnection attempts to the current server list. 1310 */ 1311 public void disconnect() { 1312 NetworkTablesJNI.disconnect(m_handle); 1313 } 1314 1315 /** 1316 * Starts requesting server address from Driver Station. This connects to the Driver Station 1317 * running on localhost to obtain the server IP address, and connects with the default port. 1318 */ 1319 public void startDSClient() { 1320 startDSClient(0); 1321 } 1322 1323 /** 1324 * Starts requesting server address from Driver Station. This connects to the Driver Station 1325 * running on localhost to obtain the server IP address. 1326 * 1327 * @param port server port to use in combination with IP from DS (0=default) 1328 */ 1329 public void startDSClient(int port) { 1330 NetworkTablesJNI.startDSClient(m_handle, port); 1331 } 1332 1333 /** Stops requesting server address from Driver Station. */ 1334 public void stopDSClient() { 1335 NetworkTablesJNI.stopDSClient(m_handle); 1336 } 1337 1338 /** 1339 * Flushes all updated values immediately to the local client/server. This does not flush to the 1340 * network. 1341 */ 1342 public void flushLocal() { 1343 NetworkTablesJNI.flushLocal(m_handle); 1344 } 1345 1346 /** 1347 * Flushes all updated values immediately to the network. Note: This is rate-limited to protect 1348 * the network from flooding. This is primarily useful for synchronizing network updates with 1349 * user code. 1350 */ 1351 public void flush() { 1352 NetworkTablesJNI.flush(m_handle); 1353 } 1354 1355 /** 1356 * Gets information on the currently established network connections. If operating as a client, 1357 * this will return either zero or one values. 1358 * 1359 * @return array of connection information 1360 */ 1361 public ConnectionInfo[] getConnections() { 1362 return NetworkTablesJNI.getConnections(m_handle); 1363 } 1364 1365 /** 1366 * Return whether or not the instance is connected to another node. 1367 * 1368 * @return True if connected. 1369 */ 1370 public boolean isConnected() { 1371 return NetworkTablesJNI.isConnected(m_handle); 1372 } 1373 1374 /** 1375 * Get the time offset between server time and local time. Add this value to local time to get 1376 * the estimated equivalent server time. In server mode, this always returns 0. In client mode, 1377 * this returns the time offset only if the client and server are connected and have exchanged 1378 * synchronization messages. Note the time offset may change over time as it is periodically 1379 * updated; to receive updates as events, add a listener to the "time sync" event. 1380 * 1381 * @return Time offset in microseconds (optional) 1382 */ 1383 public OptionalLong getServerTimeOffset() { 1384 return NetworkTablesJNI.getServerTimeOffset(m_handle); 1385 } 1386 1387 /** 1388 * Starts logging entry changes to a DataLog. 1389 * 1390 * @param log data log object; lifetime must extend until StopEntryDataLog is called or the 1391 * instance is destroyed 1392 * @param prefix only store entries with names that start with this prefix; the prefix is not 1393 * included in the data log entry name 1394 * @param logPrefix prefix to add to data log entry names 1395 * @return Data logger handle 1396 */ 1397 public int startEntryDataLog(DataLog log, String prefix, String logPrefix) { 1398 return NetworkTablesJNI.startEntryDataLog(m_handle, log, prefix, logPrefix); 1399 } 1400 1401 /** 1402 * Stops logging entry changes to a DataLog. 1403 * 1404 * @param logger data logger handle 1405 */ 1406 public static void stopEntryDataLog(int logger) { 1407 NetworkTablesJNI.stopEntryDataLog(logger); 1408 } 1409 1410 /** 1411 * Starts logging connection changes to a DataLog. 1412 * 1413 * @param log data log object; lifetime must extend until StopConnectionDataLog is called or the 1414 * instance is destroyed 1415 * @param name data log entry name 1416 * @return Data logger handle 1417 */ 1418 public int startConnectionDataLog(DataLog log, String name) { 1419 return NetworkTablesJNI.startConnectionDataLog(m_handle, log, name); 1420 } 1421 1422 /** 1423 * Stops logging connection changes to a DataLog. 1424 * 1425 * @param logger data logger handle 1426 */ 1427 public static void stopConnectionDataLog(int logger) { 1428 NetworkTablesJNI.stopConnectionDataLog(logger); 1429 } 1430 1431 /** 1432 * Add logger callback function. By default, log messages are sent to stderr; this function sends 1433 * log messages with the specified levels to the provided callback function instead. The callback 1434 * function will only be called for log messages with level greater than or equal to minLevel and 1435 * less than or equal to maxLevel; messages outside this range will be silently ignored. 1436 * 1437 * @param minLevel minimum log level 1438 * @param maxLevel maximum log level 1439 * @param func callback function 1440 * @return Listener handle 1441 */ 1442 public int addLogger(int minLevel, int maxLevel, Consumer<NetworkTableEvent> func) { 1443 return m_listeners.addLogger(minLevel, maxLevel, func); 1444 } 1445 1446 /** 1447 * Returns whether there is a data schema already registered with the given name that this 1448 * instance has published. This does NOT perform a check as to whether the schema has already 1449 * been published by another node on the network. 1450 * 1451 * @param name Name (the string passed as the data type for topics using this schema) 1452 * @return True if schema already registered 1453 */ 1454 public boolean hasSchema(String name) { 1455 return m_schemas.containsKey("/.schema/" + name); 1456 } 1457 1458 /** 1459 * Registers a data schema. Data schemas provide information for how a certain data type string 1460 * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. 1461 * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas 1462 * are published just like normal topics, with the name being generated from the provided name: 1463 * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. 1464 * 1465 * @param name Name (the string passed as the data type for topics using this schema) 1466 * @param type Type of schema (e.g. "protobuf", "struct", etc) 1467 * @param schema Schema data 1468 */ 1469 public void addSchema(String name, String type, byte[] schema) { 1470 m_schemas.computeIfAbsent("/.schema/" + name, k -> { 1471 RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}"); 1472 pub.setDefault(schema); 1473 return pub; 1474 }); 1475 } 1476 1477 /** 1478 * Registers a data schema. Data schemas provide information for how a certain data type string 1479 * can be decoded. The type string of a data schema indicates the type of the schema itself (e.g. 1480 * "protobuf" for protobuf schemas, "struct" for struct schemas, etc). In NetworkTables, schemas 1481 * are published just like normal topics, with the name being generated from the provided name: 1482 * "/.schema/name". Duplicate calls to this function with the same name are silently ignored. 1483 * 1484 * @param name Name (the string passed as the data type for topics using this schema) 1485 * @param type Type of schema (e.g. "protobuf", "struct", etc) 1486 * @param schema Schema data 1487 */ 1488 public void addSchema(String name, String type, String schema) { 1489 m_schemas.computeIfAbsent("/.schema/" + name, k -> { 1490 RawPublisher pub = getRawTopic(k).publishEx(type, "{\"retained\":true}"); 1491 pub.setDefault(StandardCharsets.UTF_8.encode(schema)); 1492 return pub; 1493 }); 1494 } 1495 1496 /** 1497 * Registers a protobuf schema. Duplicate calls to this function with the same name are silently 1498 * ignored. 1499 * 1500 * @param proto protobuf serialization object 1501 */ 1502 public void addSchema(Protobuf<?, ?> proto) { 1503 proto.forEachDescriptor( 1504 this::hasSchema, 1505 (typeString, schema) -> addSchema(typeString, "proto:FileDescriptorProto", schema)); 1506 } 1507 1508 /** 1509 * Registers a struct schema. Duplicate calls to this function with the same name are silently 1510 * ignored. 1511 * 1512 * @param struct struct serialization object 1513 */ 1514 public void addSchema(Struct<?> struct) { 1515 addSchemaImpl(struct, new HashSet<>()); 1516 } 1517 1518 @Override 1519 public boolean equals(Object other) { 1520 if (other == this) { 1521 return true; 1522 } 1523 if (!(other instanceof NetworkTableInstance)) { 1524 return false; 1525 } 1526 1527 return m_handle == ((NetworkTableInstance) other).m_handle; 1528 } 1529 1530 @Override 1531 public int hashCode() { 1532 return m_handle; 1533 } 1534 1535 private void addSchemaImpl(Struct<?> struct, Set<String> seen) { 1536 String typeString = struct.getTypeString(); 1537 if (hasSchema(typeString)) { 1538 return; 1539 } 1540 if (!seen.add(typeString)) { 1541 throw new UnsupportedOperationException(typeString + ": circular reference with " + seen); 1542 } 1543 addSchema(typeString, "structschema", struct.getSchema()); 1544 for (Struct<?> inner : struct.getNested()) { 1545 addSchemaImpl(inner, seen); 1546 } 1547 seen.remove(typeString); 1548 } 1549 1550 private boolean m_owned; 1551 private int m_handle; 1552 private final ConcurrentMap<String, RawPublisher> m_schemas = new ConcurrentHashMap<>(); 1553}