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