mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-23 01:21:42 +00:00
Implement independent instances.
Previously, most of the classes were implemented as singletons so only one
instance was possible.
This change adds an instance handle-based API. In Java, this API is located
in a different package than the old API (edu.wpi.first.networktables).
Backwards compatibility with ITable and the old NetworkTable API is largely
maintained, but a handful of classes have moved to the new package in Java
(ConnectionInfo and PersistentException), and the old JNI has been completed
replaced.
Also:
- Move SetTeam implementation to Dispatcher.
- Consistently pass time through Java and C++ Value API.
- Rename nt_Value.h to NetworkTableValue.h for consistency with Java.
- Improve documentation
- Make C++ and Java APIs more consistent
- Document RPC functions and support RPC in Java.
- Add polling features for entry and connection listeners and use them to
move callback threads to Java level.
- Remove thread start and stop hooks (as polling is available).
- Make Notifiers, RpcServer, Dispatcher, and Storage mockable.
- Set NOTIFY_NEW on immediate entry notifications.
- Make GetTable("/") and GetTable("") equivalent.
- Generate local notification for flags update when loading persistent file.
And many unit test updates/changes:
- Use InitGoogleMock instead of InitGoogleTest in test main.
- Move test printers to TestPrinter.h/cpp.
- Provide printers for StringRef, EntryNotifier, and Handle.
- StorageTest: Check notifications.
- Add entry notifier unit tests.
- Storage: Add test for incoming entry assignment.
- Update connection listener tests.
- Add entry listener unit tests.
Fixes #11, #140, #189, #190, #192, #193, #221
This commit is contained in:
@@ -9,84 +9,107 @@
|
||||
#define NT_STORAGE_H_
|
||||
|
||||
#include <atomic>
|
||||
#include <condition_variable>
|
||||
#include <cstddef>
|
||||
#include <fstream>
|
||||
#include <functional>
|
||||
#include <iosfwd>
|
||||
#include <memory>
|
||||
#include <mutex>
|
||||
#include <vector>
|
||||
|
||||
#include "llvm/DenseMap.h"
|
||||
#include "llvm/SmallSet.h"
|
||||
#include "llvm/StringMap.h"
|
||||
#include "support/atomic_static.h"
|
||||
#include "Message.h"
|
||||
#include "Notifier.h"
|
||||
#include "ntcore_cpp.h"
|
||||
#include "RpcServer.h"
|
||||
#include "SequenceNumber.h"
|
||||
|
||||
#include "IStorage.h"
|
||||
|
||||
namespace wpi {
|
||||
class Logger;
|
||||
}
|
||||
|
||||
namespace nt {
|
||||
|
||||
class NetworkConnection;
|
||||
class StorageTest;
|
||||
class IEntryNotifier;
|
||||
class IRpcServer;
|
||||
class IStorageTest;
|
||||
|
||||
class Storage {
|
||||
class Storage : public IStorage {
|
||||
friend class StorageTest;
|
||||
|
||||
public:
|
||||
static Storage& GetInstance() {
|
||||
ATOMIC_STATIC(Storage, instance);
|
||||
return instance;
|
||||
}
|
||||
Storage(IEntryNotifier& notifier, IRpcServer& rpcserver, wpi::Logger& logger);
|
||||
Storage(const Storage&) = delete;
|
||||
Storage& operator=(const Storage&) = delete;
|
||||
|
||||
~Storage();
|
||||
|
||||
// Accessors required by Dispatcher. A function pointer is used for
|
||||
// Accessors required by Dispatcher. An interface is used for
|
||||
// generation of outgoing messages to break a dependency loop between
|
||||
// Storage and Dispatcher; in operation this is always set to
|
||||
// Dispatcher::QueueOutgoing.
|
||||
typedef std::function<void(std::shared_ptr<Message> msg,
|
||||
NetworkConnection* only,
|
||||
NetworkConnection* except)>
|
||||
QueueOutgoingFunc;
|
||||
void SetOutgoing(QueueOutgoingFunc queue_outgoing, bool server);
|
||||
void ClearOutgoing();
|
||||
// Storage and Dispatcher.
|
||||
void SetDispatcher(IDispatcher* dispatcher, bool server) override;
|
||||
void ClearDispatcher() override;
|
||||
|
||||
// Required for wire protocol 2.0 to get the entry type of an entry when
|
||||
// receiving entry updates (because the length/type is not provided in the
|
||||
// message itself). Not used in wire protocol 3.0.
|
||||
NT_Type GetEntryType(unsigned int id) const;
|
||||
NT_Type GetMessageEntryType(unsigned int id) const override;
|
||||
|
||||
void ProcessIncoming(std::shared_ptr<Message> msg, NetworkConnection* conn,
|
||||
std::weak_ptr<NetworkConnection> conn_weak);
|
||||
void GetInitialAssignments(NetworkConnection& conn,
|
||||
std::vector<std::shared_ptr<Message>>* msgs);
|
||||
void ApplyInitialAssignments(NetworkConnection& conn,
|
||||
llvm::ArrayRef<std::shared_ptr<Message>> msgs,
|
||||
bool new_server,
|
||||
std::vector<std::shared_ptr<Message>>* out_msgs);
|
||||
std::weak_ptr<NetworkConnection> conn_weak) override;
|
||||
void GetInitialAssignments(
|
||||
NetworkConnection& conn,
|
||||
std::vector<std::shared_ptr<Message>>* msgs) override;
|
||||
void ApplyInitialAssignments(
|
||||
NetworkConnection& conn, llvm::ArrayRef<std::shared_ptr<Message>> msgs,
|
||||
bool new_server,
|
||||
std::vector<std::shared_ptr<Message>>* out_msgs) override;
|
||||
|
||||
// User functions. These are the actual implementations of the corresponding
|
||||
// user API functions in ntcore_cpp.
|
||||
std::shared_ptr<Value> GetEntryValue(StringRef name) const;
|
||||
std::shared_ptr<Value> GetEntryValue(unsigned int local_id) const;
|
||||
|
||||
bool SetDefaultEntryValue(StringRef name, std::shared_ptr<Value> value);
|
||||
bool SetDefaultEntryValue(unsigned int local_id,
|
||||
std::shared_ptr<Value> value);
|
||||
|
||||
bool SetEntryValue(StringRef name, std::shared_ptr<Value> value);
|
||||
bool SetEntryValue(unsigned int local_id, std::shared_ptr<Value> value);
|
||||
|
||||
void SetEntryTypeValue(StringRef name, std::shared_ptr<Value> value);
|
||||
void SetEntryTypeValue(unsigned int local_id, std::shared_ptr<Value> value);
|
||||
|
||||
void SetEntryFlags(StringRef name, unsigned int flags);
|
||||
void SetEntryFlags(unsigned int local_id, unsigned int flags);
|
||||
|
||||
unsigned int GetEntryFlags(StringRef name) const;
|
||||
unsigned int GetEntryFlags(unsigned int local_id) const;
|
||||
|
||||
void DeleteEntry(StringRef name);
|
||||
void DeleteEntry(unsigned int local_id);
|
||||
|
||||
void DeleteAllEntries();
|
||||
std::vector<EntryInfo> GetEntryInfo(StringRef prefix, unsigned int types);
|
||||
void NotifyEntries(StringRef prefix,
|
||||
EntryListenerCallback only = nullptr) const;
|
||||
|
||||
std::vector<EntryInfo> GetEntryInfo(int inst, StringRef prefix,
|
||||
unsigned int types);
|
||||
|
||||
// Index-only
|
||||
unsigned int GetEntry(StringRef name);
|
||||
std::vector<unsigned int> GetEntries(StringRef prefix, unsigned int types);
|
||||
EntryInfo GetEntryInfo(int inst, unsigned int local_id) const;
|
||||
std::string GetEntryName(unsigned int local_id) const;
|
||||
NT_Type GetEntryType(unsigned int local_id) const;
|
||||
unsigned long long GetEntryLastChange(unsigned int local_id) const;
|
||||
|
||||
// Filename-based save/load functions. Used both by periodic saves and
|
||||
// accessible directly via the user API.
|
||||
const char* SavePersistent(StringRef filename, bool periodic) const;
|
||||
const char* SavePersistent(StringRef filename, bool periodic) const override;
|
||||
const char* LoadPersistent(
|
||||
StringRef filename,
|
||||
std::function<void(std::size_t line, const char* msg)> warn);
|
||||
std::function<void(std::size_t line, const char* msg)> warn) override;
|
||||
|
||||
// Stream-based save/load functions (exposed for testing purposes). These
|
||||
// implement the guts of the filename-based functions.
|
||||
@@ -97,25 +120,18 @@ class Storage {
|
||||
|
||||
// RPC configuration needs to come through here as RPC definitions are
|
||||
// actually special Storage value types.
|
||||
void CreateRpc(StringRef name, StringRef def, RpcCallback callback);
|
||||
void CreatePolledRpc(StringRef name, StringRef def);
|
||||
|
||||
unsigned int CallRpc(StringRef name, StringRef params);
|
||||
bool GetRpcResult(bool blocking, unsigned int call_uid, std::string* result);
|
||||
bool GetRpcResult(bool blocking, unsigned int call_uid, double time_out,
|
||||
void CreateRpc(unsigned int local_id, StringRef def, unsigned int rpc_uid);
|
||||
unsigned int CallRpc(unsigned int local_id, StringRef params);
|
||||
bool GetRpcResult(unsigned int local_id, unsigned int call_uid,
|
||||
std::string* result);
|
||||
void CancelBlockingRpcResult(unsigned int call_uid);
|
||||
bool GetRpcResult(unsigned int local_id, unsigned int call_uid,
|
||||
std::string* result, double timeout, bool* timed_out);
|
||||
void CancelRpcResult(unsigned int local_id, unsigned int call_uid);
|
||||
|
||||
private:
|
||||
Storage();
|
||||
Storage(Notifier& notifier, RpcServer& rpcserver);
|
||||
Storage(const Storage&) = delete;
|
||||
Storage& operator=(const Storage&) = delete;
|
||||
|
||||
// Data for each table entry.
|
||||
struct Entry {
|
||||
Entry(llvm::StringRef name_)
|
||||
: name(name_), flags(0), id(0xffff), rpc_call_uid(0) {}
|
||||
Entry(llvm::StringRef name_) : name(name_) {}
|
||||
bool IsPersistent() const { return (flags & NT_PERSISTENT) != 0; }
|
||||
|
||||
// We redundantly store the name so that it's available when accessing the
|
||||
@@ -124,34 +140,38 @@ class Storage {
|
||||
|
||||
// The current value and flags.
|
||||
std::shared_ptr<Value> value;
|
||||
unsigned int flags;
|
||||
unsigned int flags{0};
|
||||
|
||||
// Unique ID for this entry as used in network messages. The value is
|
||||
// assigned by the server, so on the client this is 0xffff until an
|
||||
// entry assignment is received back from the server.
|
||||
unsigned int id;
|
||||
unsigned int id{0xffff};
|
||||
|
||||
// Local ID.
|
||||
unsigned int local_id{UINT_MAX};
|
||||
|
||||
// Sequence number for update resolution.
|
||||
SequenceNumber seq_num;
|
||||
|
||||
// RPC callback function. Null if either not an RPC or if the RPC is
|
||||
// polled.
|
||||
RpcCallback rpc_callback;
|
||||
// RPC handle.
|
||||
unsigned int rpc_uid{UINT_MAX};
|
||||
|
||||
// Last UID used when calling this RPC (primarily for client use). This
|
||||
// is incremented for each call.
|
||||
unsigned int rpc_call_uid;
|
||||
unsigned int rpc_call_uid{0};
|
||||
};
|
||||
|
||||
typedef llvm::StringMap<std::unique_ptr<Entry>> EntriesMap;
|
||||
typedef llvm::StringMap<Entry*> EntriesMap;
|
||||
typedef std::vector<Entry*> IdMap;
|
||||
typedef llvm::DenseMap<std::pair<unsigned int, unsigned int>, std::string>
|
||||
RpcResultMap;
|
||||
typedef llvm::SmallSet<unsigned int, 12> RpcBlockingCallSet;
|
||||
typedef std::vector<std::unique_ptr<Entry>> LocalMap;
|
||||
typedef std::pair<unsigned int, unsigned int> RpcIdPair;
|
||||
typedef llvm::DenseMap<RpcIdPair, std::string> RpcResultMap;
|
||||
typedef llvm::SmallSet<RpcIdPair, 12> RpcBlockingCallSet;
|
||||
|
||||
mutable std::mutex m_mutex;
|
||||
EntriesMap m_entries;
|
||||
IdMap m_idmap;
|
||||
LocalMap m_localmap;
|
||||
RpcResultMap m_rpc_results;
|
||||
RpcBlockingCallSet m_rpc_blocking_calls;
|
||||
// If any persistent values have changed
|
||||
@@ -162,20 +182,27 @@ class Storage {
|
||||
std::condition_variable m_rpc_results_cond;
|
||||
|
||||
// configured by dispatcher at startup
|
||||
QueueOutgoingFunc m_queue_outgoing;
|
||||
IDispatcher* m_dispatcher = nullptr;
|
||||
bool m_server = true;
|
||||
|
||||
// references to singletons (we don't grab them directly for testing purposes)
|
||||
Notifier& m_notifier;
|
||||
RpcServer& m_rpc_server;
|
||||
IEntryNotifier& m_notifier;
|
||||
IRpcServer& m_rpc_server;
|
||||
wpi::Logger& m_logger;
|
||||
|
||||
bool GetPersistentEntries(
|
||||
bool periodic,
|
||||
std::vector<std::pair<std::string, std::shared_ptr<Value>>>* entries)
|
||||
const;
|
||||
void DeleteAllEntriesImpl();
|
||||
void SetEntryValueImpl(Entry* entry, std::shared_ptr<Value> value,
|
||||
std::unique_lock<std::mutex>& lock, bool local);
|
||||
void SetEntryFlagsImpl(Entry* entry, unsigned int flags,
|
||||
std::unique_lock<std::mutex>& lock, bool local);
|
||||
void DeleteEntryImpl(Entry* entry, EntriesMap::iterator it,
|
||||
std::unique_lock<std::mutex>& lock, bool local);
|
||||
|
||||
ATOMIC_STATIC_DECL(Storage)
|
||||
// Must be called with m_mutex held
|
||||
void DeleteAllEntriesImpl(bool local);
|
||||
Entry* GetOrNew(StringRef name, bool* is_new = nullptr);
|
||||
};
|
||||
|
||||
} // namespace nt
|
||||
|
||||
Reference in New Issue
Block a user