diff --git a/build.gradle b/build.gradle index 828c992..fd23a92 100644 --- a/build.gradle +++ b/build.gradle @@ -90,12 +90,12 @@ dependencies { model { jniConfigs { ntcore(JNIConfig) { - jniDefinitionClasses = [ "edu.wpi.first.wpilibj.networktables.NetworkTablesJNI" ] + jniDefinitionClasses = [ "edu.wpi.first.networktables.NetworkTablesJNI" ] jniArmHeaderLocations = [ all: file("${rootDir}/src/arm-linux-jni") ] sourceSets = [ project.sourceSets.main ] } ntcoreJNI(JNIConfig) { - jniDefinitionClasses = [ "edu.wpi.first.wpilibj.networktables.NetworkTablesJNI" ] + jniDefinitionClasses = [ "edu.wpi.first.networktables.NetworkTablesJNI" ] jniArmHeaderLocations = [ all: file("${rootDir}/src/arm-linux-jni") ] sourceSets = [ project.sourceSets.main ] } diff --git a/manualTests/native/client.cpp b/manualTests/native/client.cpp index 15c1ee4..a5ab584 100644 --- a/manualTests/native/client.cpp +++ b/manualTests/native/client.cpp @@ -1,25 +1,34 @@ #include +#include #include #include #include "ntcore.h" int main() { - nt::SetLogger( - [](unsigned int level, const char* file, unsigned int line, - const char* msg) { - std::fputs(msg, stderr); + auto inst = nt::GetDefaultInstance(); + nt::AddLogger( + inst, + [](const nt::LogMessage& msg) { + std::fputs(msg.message.c_str(), stderr); std::fputc('\n', stderr); }, - 0); - nt::StartClient("127.0.0.1", 10000); + 0, UINT_MAX); + nt::StartClient(inst, "127.0.0.1", 10000); std::this_thread::sleep_for(std::chrono::seconds(2)); - auto foo = nt::GetEntryValue("/foo"); - if (foo && foo->IsDouble()) printf("Got foo: %g\n", foo->GetDouble()); - nt::SetEntryValue("/bar", nt::Value::MakeBoolean(false)); - nt::SetEntryFlags("/bar", NT_PERSISTENT); - nt::SetEntryValue("/bar2", nt::Value::MakeBoolean(true)); - nt::SetEntryValue("/bar2", nt::Value::MakeBoolean(false)); - nt::SetEntryValue("/bar2", nt::Value::MakeBoolean(true)); + + auto foo = nt::GetEntry(inst, "/foo"); + auto foo_val = nt::GetEntryValue(foo); + if (foo_val && foo_val->IsDouble()) + printf("Got foo: %g\n", foo_val->GetDouble()); + + auto bar = nt::GetEntry(inst, "/bar"); + nt::SetEntryValue(bar, nt::Value::MakeBoolean(false)); + nt::SetEntryFlags(bar, NT_PERSISTENT); + + auto bar2 = nt::GetEntry(inst, "/bar2"); + nt::SetEntryValue(bar2, nt::Value::MakeBoolean(true)); + nt::SetEntryValue(bar2, nt::Value::MakeBoolean(false)); + nt::SetEntryValue(bar2, nt::Value::MakeBoolean(true)); std::this_thread::sleep_for(std::chrono::seconds(10)); } diff --git a/manualTests/native/rpc_local.cpp b/manualTests/native/rpc_local.cpp index bec06fe..914145c 100644 --- a/manualTests/native/rpc_local.cpp +++ b/manualTests/native/rpc_local.cpp @@ -1,48 +1,60 @@ #include +#include #include #include +#include "support/json.h" + #include "ntcore.h" -std::string callback1(nt::StringRef name, nt::StringRef params_str, - const nt::ConnectionInfo& conn_info) { - auto params = nt::UnpackRpcValues(params_str, NT_DOUBLE); - if (params.empty()) { - std::fputs("empty params?\n", stderr); - return ""; +void callback1(const nt::RpcAnswer& answer) { + wpi::json params; + try { + params = wpi::json::from_cbor(answer.params); + } catch (wpi::json::parse_error err) { + std::fputs("could not decode params?\n", stderr); + return; + } + if (!params.is_number()) { + std::fputs("did not get number\n", stderr); + return; } - std::fprintf(stderr, "called with %g\n", params[0]->GetDouble()); + double val = params.get(); + std::fprintf(stderr, "called with %g\n", val); - return nt::PackRpcValues(nt::Value::MakeDouble(params[0]->GetDouble() + 1.2)); + answer.PostResponse(wpi::json::to_cbor(val + 1.2)); } int main() { - nt::SetLogger( - [](unsigned int level, const char* file, unsigned int line, - const char* msg) { - std::fputs(msg, stderr); + auto inst = nt::GetDefaultInstance(); + nt::AddLogger( + inst, + [](const nt::LogMessage& msg) { + std::fputs(msg.message.c_str(), stderr); std::fputc('\n', stderr); }, - 0); + 0, UINT_MAX); - nt::RpcDefinition def; - def.version = 1; - def.name = "myfunc1"; - def.params.emplace_back("param1", nt::Value::MakeDouble(0.0)); - def.results.emplace_back("result1", NT_DOUBLE); - nt::CreateRpc("func1", nt::PackRpcDefinition(def), callback1); + nt::StartServer(inst, "rpc_local.ini", "", 10000); + auto entry = nt::GetEntry(inst, "func1"); + nt::CreateRpc(entry, nt::StringRef("", 1), callback1); std::fputs("calling rpc\n", stderr); - unsigned int call1_uid = - nt::CallRpc("func1", nt::PackRpcValues(nt::Value::MakeDouble(2.0))); + unsigned int call1_uid = nt::CallRpc(entry, wpi::json::to_cbor(2.0)); std::string call1_result_str; std::fputs("waiting for rpc result\n", stderr); - nt::GetRpcResult(true, call1_uid, &call1_result_str); - auto call1_result = nt::UnpackRpcValues(call1_result_str, NT_DOUBLE); - if (call1_result.empty()) { - std::fputs("empty result?\n", stderr); + nt::GetRpcResult(entry, call1_uid, &call1_result_str); + wpi::json call1_result; + try { + call1_result = wpi::json::from_cbor(call1_result_str); + } catch (wpi::json::parse_error err) { + std::fputs("could not decode result?\n", stderr); + return 1; + } + if (!call1_result.is_number()) { + std::fputs("result is not number?\n", stderr); return 1; } - std::fprintf(stderr, "got %g\n", call1_result[0]->GetDouble()); + std::fprintf(stderr, "got %g\n", call1_result.get()); return 0; } diff --git a/manualTests/native/rpc_speed.cpp b/manualTests/native/rpc_speed.cpp index e8513a9..a6ee2f0 100644 --- a/manualTests/native/rpc_speed.cpp +++ b/manualTests/native/rpc_speed.cpp @@ -1,38 +1,50 @@ #include +#include #include #include #include +#include "support/json.h" + #include "ntcore.h" -std::string callback1(nt::StringRef name, nt::StringRef params_str, - const nt::ConnectionInfo& conn_info) { - auto params = nt::UnpackRpcValues(params_str, NT_DOUBLE); - if (params.empty()) { - std::fputs("empty params?\n", stderr); - return ""; +void callback1(const nt::RpcAnswer& answer) { + wpi::json params; + try { + params = wpi::json::from_cbor(answer.params); + } catch (wpi::json::parse_error err) { + std::fputs("could not decode params?\n", stderr); + return; + } + if (!params.is_number()) { + std::fputs("did not get number\n", stderr); + return; } - return nt::PackRpcValues(nt::Value::MakeDouble(params[0]->GetDouble() + 1.2)); + double val = params.get(); + answer.PostResponse(wpi::json::to_cbor(val + 1.2)); } int main() { - nt::RpcDefinition def; - def.version = 1; - def.name = "myfunc1"; - def.params.emplace_back("param1", nt::Value::MakeDouble(0.0)); - def.results.emplace_back("result1", NT_DOUBLE); - nt::CreateRpc("func1", nt::PackRpcDefinition(def), callback1); + auto inst = nt::GetDefaultInstance(); + nt::StartServer(inst, "rpc_speed.ini", "", 10000); + auto entry = nt::GetEntry(inst, "func1"); + nt::CreateRpc(entry, nt::StringRef("", 1), callback1); std::string call1_result_str; auto start2 = std::chrono::high_resolution_clock::now(); auto start = nt::Now(); for (int i=0; i<10000; ++i) { - unsigned int call1_uid = - nt::CallRpc("func1", nt::PackRpcValues(nt::Value::MakeDouble(i))); - nt::GetRpcResult(true, call1_uid, &call1_result_str); - auto call1_result = nt::UnpackRpcValues(call1_result_str, NT_DOUBLE); - if (call1_result.empty()) { - std::fputs("empty result?\n", stderr); + unsigned int call1_uid = nt::CallRpc(entry, wpi::json::to_cbor(i)); + nt::GetRpcResult(entry, call1_uid, &call1_result_str); + wpi::json call1_result; + try { + call1_result = wpi::json::from_cbor(call1_result_str); + } catch (wpi::json::parse_error err) { + std::fputs("could not decode result?\n", stderr); + return 1; + } + if (!call1_result.is_number()) { + std::fputs("result is not number?\n", stderr); return 1; } } diff --git a/manualTests/native/server.cpp b/manualTests/native/server.cpp index 8360e13..8775aed 100644 --- a/manualTests/native/server.cpp +++ b/manualTests/native/server.cpp @@ -1,24 +1,31 @@ #include +#include #include #include #include "ntcore.h" int main() { - nt::SetLogger( - [](unsigned int level, const char* file, unsigned int line, - const char* msg) { - std::fputs(msg, stderr); + auto inst = nt::GetDefaultInstance(); + nt::AddLogger( + inst, + [](const nt::LogMessage& msg) { + std::fputs(msg.message.c_str(), stderr); std::fputc('\n', stderr); }, - 0); - nt::StartServer("persistent.ini", "", 10000); + 0, UINT_MAX); + nt::StartServer(inst, "persistent.ini", "", 10000); std::this_thread::sleep_for(std::chrono::seconds(1)); - nt::SetEntryValue("/foo", nt::Value::MakeDouble(0.5)); - nt::SetEntryFlags("/foo", NT_PERSISTENT); - nt::SetEntryValue("/foo2", nt::Value::MakeDouble(0.5)); - nt::SetEntryValue("/foo2", nt::Value::MakeDouble(0.7)); - nt::SetEntryValue("/foo2", nt::Value::MakeDouble(0.6)); - nt::SetEntryValue("/foo2", nt::Value::MakeDouble(0.5)); + + auto foo = nt::GetEntry(inst, "/foo"); + nt::SetEntryValue(foo, nt::Value::MakeDouble(0.5)); + nt::SetEntryFlags(foo, NT_PERSISTENT); + + auto foo2 = nt::GetEntry(inst, "/foo2"); + nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.5)); + nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.7)); + nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.6)); + nt::SetEntryValue(foo2, nt::Value::MakeDouble(0.5)); + std::this_thread::sleep_for(std::chrono::seconds(10)); } diff --git a/src/dev/java/edu/wpi/first/ntcore/DevMain.java b/src/dev/java/edu/wpi/first/ntcore/DevMain.java index a6b4699..99166d2 100644 --- a/src/dev/java/edu/wpi/first/ntcore/DevMain.java +++ b/src/dev/java/edu/wpi/first/ntcore/DevMain.java @@ -1,12 +1,12 @@ package edu.wpi.first.ntcore; -import edu.wpi.first.wpilibj.networktables.NetworkTablesJNI; +import edu.wpi.first.networktables.NetworkTablesJNI; import edu.wpi.first.wpiutil.RuntimeDetector; public class DevMain { public static void main(String[] args) { System.out.println("Hello World!"); System.out.println(RuntimeDetector.getPlatformPath()); - NetworkTablesJNI.flush(); + NetworkTablesJNI.flush(NetworkTablesJNI.getDefaultInstance()); } } diff --git a/src/dev/native/cpp/main.cpp b/src/dev/native/cpp/main.cpp index 505b3f4..0b93cef 100644 --- a/src/dev/native/cpp/main.cpp +++ b/src/dev/native/cpp/main.cpp @@ -1,9 +1,10 @@ #include "ntcore.h" -#include "nt_Value.h" #include int main() { - nt::SetEntryValue("MyValue", nt::Value::MakeString("Hello World")); + auto myValue = nt::GetEntry(nt::GetDefaultInstance(), "MyValue"); - std::cout << nt::GetEntryValue("MyValue")->GetString() << std::endl; + nt::SetEntryValue(myValue, nt::Value::MakeString("Hello World")); + + std::cout << nt::GetEntryValue(myValue)->GetString() << std::endl; } diff --git a/src/main/java/edu/wpi/first/networktables/ConnectionInfo.java b/src/main/java/edu/wpi/first/networktables/ConnectionInfo.java new file mode 100644 index 0000000..5cd72e2 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/ConnectionInfo.java @@ -0,0 +1,57 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * NetworkTables Connection information. + */ +public final class ConnectionInfo { + /** + * The remote identifier (as set on the remote node by + * {@link NetworkTableInstance#setNetworkIdentity(String)}). + */ + public final String remote_id; + + /** + * The IP address of the remote node. + */ + public final String remote_ip; + + /** + * The port number of the remote node. + */ + public final int remote_port; + + /** + * The last time any update was received from the remote node (same scale as + * returned by {@link NetworkTablesJNI#now()}). + */ + public final long last_update; + + /** + * The protocol version being used for this connection. This is in protocol + * layer format, so 0x0200 = 2.0, 0x0300 = 3.0). + */ + public final int protocol_version; + + /** Constructor. + * This should generally only be used internally to NetworkTables. + * @param remote_id Remote identifier + * @param remote_ip Remote IP address + * @param remote_port Remote port number + * @param last_update Last time an update was received + * @param protocol_version The protocol version used for the connection + */ + public ConnectionInfo(String remote_id, String remote_ip, int remote_port, long last_update, int protocol_version) { + this.remote_id = remote_id; + this.remote_ip = remote_ip; + this.remote_port = remote_port; + this.last_update = last_update; + this.protocol_version = protocol_version; + } +} diff --git a/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java b/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java new file mode 100644 index 0000000..391f6ea --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/ConnectionNotification.java @@ -0,0 +1,48 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * NetworkTables Connection notification. + */ +public final class ConnectionNotification { + /** + * Listener that was triggered. + */ + public final int listener; + + /** + * True if event is due to connection being established. + */ + public final boolean connected; + + /** + * Connection information. + */ + public final ConnectionInfo conn; + + /** Constructor. + * This should generally only be used internally to NetworkTables. + * @param inst Instance + * @param listener Listener that was triggered + * @param connected Connected if true + * @param conn Connection information + */ + public ConnectionNotification(NetworkTableInstance inst, int listener, boolean connected, ConnectionInfo conn) { + this.inst = inst; + this.listener = listener; + this.connected = connected; + this.conn = conn; + } + + private final NetworkTableInstance inst; + + public NetworkTableInstance getInstance() { + return inst; + } +} diff --git a/src/main/java/edu/wpi/first/networktables/EntryInfo.java b/src/main/java/edu/wpi/first/networktables/EntryInfo.java new file mode 100644 index 0000000..8d028bb --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/EntryInfo.java @@ -0,0 +1,63 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * NetworkTables Entry information. + */ +public final class EntryInfo { + /** Entry handle. */ + public final int entry; + + /** Entry name. */ + public final String name; + + /** Entry type. */ + public final NetworkTableType type; + + /** Entry flags. */ + public final int flags; + + /** Timestamp of last change to entry (type or value). */ + public final long last_change; + + /** Constructor. + * This should generally only be used internally to NetworkTables. + * @param inst Instance + * @param entry Entry handle + * @param name Name + * @param type Type (integer version of {@link NetworkTableType}) + * @param flags Flags + * @param last_change Timestamp of last change + */ + public EntryInfo(NetworkTableInstance inst, int entry, String name, int type, int flags, long last_change) { + this.inst = inst; + this.entry = entry; + this.name = name; + this.type = NetworkTableType.getFromInt(type); + this.flags = flags; + this.last_change = last_change; + } + + /* Network table instance. */ + private final NetworkTableInstance inst; + + /* Cached entry object. */ + private NetworkTableEntry entryObject; + + /** + * Get the entry as an object. + * @return NetworkTableEntry for this entry. + */ + NetworkTableEntry getEntry() { + if (entryObject == null) { + entryObject = new NetworkTableEntry(inst, entry); + } + return entryObject; + } +} diff --git a/src/main/java/edu/wpi/first/networktables/EntryListenerFlags.java b/src/main/java/edu/wpi/first/networktables/EntryListenerFlags.java new file mode 100644 index 0000000..8fa3a8a --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/EntryListenerFlags.java @@ -0,0 +1,60 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * Flag values for use with entry listeners. + * + * The flags are a bitmask and must be OR'ed together to indicate the + * combination of events desired to be received. + * + * The constants kNew, kDelete, kUpdate, and kFlags represent different events + * that can occur to entries. + * + * By default, notifications are only generated for remote changes occurring + * after the listener is created. The constants kImmediate and kLocal are + * modifiers that cause notifications to be generated at other times. + */ +public interface EntryListenerFlags { + /** Initial listener addition. + * Set this flag to receive immediate notification of entries matching the + * flag criteria (generally only useful when combined with kNew). + */ + public static final int kImmediate = 0x01; + + /** Changed locally. + * Set this flag to receive notification of both local changes and changes + * coming from remote nodes. By default, notifications are only generated + * for remote changes. Must be combined with some combination of kNew, + * kDelete, kUpdate, and kFlags to receive notifications of those respective + * events. + */ + public static final int kLocal = 0x02; + + /** Newly created entry. + * Set this flag to receive a notification when an entry is created. + */ + public static final int kNew = 0x04; + + /** Entry was deleted. + * Set this flag to receive a notification when an entry is deleted. + */ + public static final int kDelete = 0x08; + + /** Entry's value changed. + * Set this flag to receive a notification when an entry's value (or type) + * changes. + */ + public static final int kUpdate = 0x10; + + /** Entry's flags changed. + * Set this flag to receive a notification when an entry's flags value + * changes. + */ + public static final int kFlags = 0x20; +} diff --git a/src/main/java/edu/wpi/first/networktables/EntryNotification.java b/src/main/java/edu/wpi/first/networktables/EntryNotification.java new file mode 100644 index 0000000..c18f5ac --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/EntryNotification.java @@ -0,0 +1,74 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * NetworkTables Entry notification. + */ +public final class EntryNotification { + /** + * Listener that was triggered. + */ + public final int listener; + + /** + * Entry handle. + */ + public final int entry; + + /** + * Entry name. + */ + public final String name; + + /** + * The new value. + */ + public final NetworkTableValue value; + + /** + * Update flags. For example, {@link EntryListenerFlags#kNew} if the key did + * not previously exist. + */ + public final int flags; + + /** Constructor. + * This should generally only be used internally to NetworkTables. + * @param inst Instance + * @param listener Listener that was triggered + * @param entry Entry handle + * @param name Entry name + * @param value The new value + * @param flags Update flags + */ + public EntryNotification(NetworkTableInstance inst, int listener, int entry, String name, NetworkTableValue value, int flags) { + this.inst = inst; + this.listener = listener; + this.entry = entry; + this.name = name; + this.value = value; + this.flags = flags; + } + + /* Network table instance. */ + private final NetworkTableInstance inst; + + /* Cached entry object. */ + NetworkTableEntry entryObject; + + /** + * Get the entry as an object. + * @return NetworkTableEntry for this entry. + */ + public NetworkTableEntry getEntry() { + if (entryObject == null) { + entryObject = new NetworkTableEntry(inst, entry); + } + return entryObject; + } +} diff --git a/src/main/java/edu/wpi/first/networktables/LogMessage.java b/src/main/java/edu/wpi/first/networktables/LogMessage.java new file mode 100644 index 0000000..a7cc7f3 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/LogMessage.java @@ -0,0 +1,75 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * NetworkTables log message. + */ +public final class LogMessage { + /** + * Logging levels. + */ + public static final int kCritical = 50; + public static final int kError = 40; + public static final int kWarning = 30; + public static final int kInfo = 20; + public static final int kDebug = 10; + public static final int kDebug1 = 9; + public static final int kDebug2 = 8; + public static final int kDebug3 = 7; + public static final int kDebug4 = 6; + + /** + * The logger that generated the message. + */ + public final int logger; + + /** + * Log level of the message. + */ + public final int level; + + /** + * The filename of the source file that generated the message. + */ + public final String filename; + + /** + * The line number in the source file that generated the message. + */ + public final int line; + + /** + * The message. + */ + public final String message; + + /** Constructor. + * This should generally only be used internally to NetworkTables. + * @param inst Instance + * @param logger Logger + * @param level Log level + * @param filename Filename + * @param line Line number + * @param message Message + */ + public LogMessage(NetworkTableInstance inst, int logger, int level, String filename, int line, String message) { + this.inst = inst; + this.logger = logger; + this.level = level; + this.filename = filename; + this.line = line; + this.message = message; + } + + private final NetworkTableInstance inst; + + NetworkTableInstance getInstance() { + return inst; + } +} diff --git a/src/main/java/edu/wpi/first/networktables/NetworkTable.java b/src/main/java/edu/wpi/first/networktables/NetworkTable.java new file mode 100644 index 0000000..eae400c --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/NetworkTable.java @@ -0,0 +1,283 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +import java.util.HashSet; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; + +/** + * A network table that knows its subtable path. + */ +public final class NetworkTable { + /** + * The path separator for sub-tables and keys + * + */ + public static final char PATH_SEPARATOR = '/'; + + private final String path; + private final String pathWithSep; + private final NetworkTableInstance inst; + + public NetworkTable(NetworkTableInstance inst, String path) { + this.path = path; + this.pathWithSep = path + PATH_SEPARATOR; + this.inst = inst; + } + + /** + * Gets the instance for the table. + * @return Instance + */ + public NetworkTableInstance getInstance() { return inst; } + + public String toString() { return "NetworkTable: " + path; } + + private final ConcurrentMap entries = new ConcurrentHashMap(); + + /** + * Gets the entry for a subkey. + * @param key the key name + * @return Network table entry. + */ + public NetworkTableEntry getEntry(String key) { + NetworkTableEntry entry = entries.get(key); + if (entry == null) { + entry = inst.getEntry(pathWithSep + key); + entries.putIfAbsent(key, entry); + } + return entry; + } + + /** + * Listen to keys only within this table. + * @param listener listener to add + * @param flags {@link EntryListenerFlags} bitmask + * @return Listener handle + */ + public int addEntryListener(TableEntryListener listener, int flags) { + final int prefixLen = path.length() + 1; + return inst.addEntryListener(pathWithSep, (event) -> { + String relativeKey = event.name.substring(prefixLen); + if (relativeKey.indexOf(PATH_SEPARATOR) != -1) // part of a subtable + return; + listener.valueChanged(this, relativeKey, event.getEntry(), event.value, event.flags); + }, flags); + } + + /** + * Listen to a single key. + * @param key the key name + * @param listener listener to add + * @param flags {@link EntryListenerFlags} bitmask + * @return Listener handle + */ + public int addEntryListener(String key, TableEntryListener listener, int flags) { + final NetworkTableEntry entry = getEntry(key); + return inst.addEntryListener(entry, (event) -> { + listener.valueChanged(this, key, entry, event.value, event.flags); + }, flags); + } + + /** + * Remove an entry listener. + * @param listener listener handle + */ + public void removeEntryListener(int listener) { + inst.removeEntryListener(listener); + } + + /** + * Listen for sub-table creation. + * This calls the listener once for each newly created sub-table. + * It immediately calls the listener for any existing sub-tables. + * @param listener listener to add + * @param localNotify notify local changes as well as remote + * @return Listener handle + */ + public int addSubTableListener(TableListener listener, boolean localNotify) { + int flags = EntryListenerFlags.kNew | EntryListenerFlags.kImmediate; + if (localNotify) + flags |= EntryListenerFlags.kLocal; + + final int prefixLen = path.length() + 1; + final NetworkTable parent = this; + + return inst.addEntryListener(pathWithSep, new Consumer() { + final Set notifiedTables = new HashSet(); + + @Override + public void accept(EntryNotification event) { + String relativeKey = event.name.substring(prefixLen); + int endSubTable = relativeKey.indexOf(PATH_SEPARATOR); + if (endSubTable == -1) + return; + String subTableKey = relativeKey.substring(0, endSubTable); + if (notifiedTables.contains(subTableKey)) + return; + notifiedTables.add(subTableKey); + listener.tableCreated(parent, subTableKey, parent.getSubTable(subTableKey)); + } + }, flags); + } + + /** + * Remove a sub-table listener. + * @param listener listener handle + */ + public void removeTableListener(int listener) { + inst.removeEntryListener(listener); + } + + /** + * Returns the table at the specified key. If there is no table at the + * specified key, it will create a new table + * + * @param key the name of the table relative to this one + * @return a sub table relative to this one + */ + public NetworkTable getSubTable(String key) { + return new NetworkTable(inst, pathWithSep + key); + } + + /** + * Checks the table and tells if it contains the specified key + * + * @param key the key to search for + * @return true if the table as a value assigned to the given key + */ + public boolean containsKey(String key) { + return getEntry(key).exists(); + } + + /** + * @param key the key to search for + * @return true if there is a subtable with the key which contains at least + * one key/subtable of its own + */ + public boolean containsSubTable(String key) { + int[] handles = NetworkTablesJNI.getEntries(inst.getHandle(), pathWithSep + key + PATH_SEPARATOR, 0); + return handles.length != 0; + } + + /** + * Gets all keys in the table (not including sub-tables). + * @param types bitmask of types; 0 is treated as a "don't care". + * @return keys currently in the table + */ + public Set getKeys(int types) { + Set keys = new HashSet(); + int prefixLen = path.length() + 1; + for (EntryInfo info : inst.getEntryInfo(pathWithSep, types)) { + String relativeKey = info.name.substring(prefixLen); + if (relativeKey.indexOf(PATH_SEPARATOR) != -1) + continue; + keys.add(relativeKey); + // populate entries as we go + if (entries.get(relativeKey) == null) { + entries.putIfAbsent(relativeKey, new NetworkTableEntry(inst, info.entry)); + } + } + return keys; + } + + /** + * Gets all keys in the table (not including sub-tables). + * @return keys currently in the table + */ + public Set getKeys() { + return getKeys(0); + } + + /** + * Gets the names of all subtables in the table. + * @return subtables currently in the table + */ + public Set getSubTables() { + Set keys = new HashSet(); + int prefixLen = path.length() + 1; + for (EntryInfo info : inst.getEntryInfo(pathWithSep, 0)) { + String relativeKey = info.name.substring(prefixLen); + int endSubTable = relativeKey.indexOf(PATH_SEPARATOR); + if (endSubTable == -1) + continue; + keys.add(relativeKey.substring(0, endSubTable)); + } + return keys; + } + + /** + * Deletes the specified key in this table. The key can + * not be null. + * + * @param key the key name + */ + public void delete(String key) { + getEntry(key).delete(); + } + + /** + * Put a value in the table + * + * @param key the key to be assigned to + * @param value the value that will be assigned + * @return False if the table key already exists with a different type + */ + boolean putValue(String key, NetworkTableValue value) { + return getEntry(key).setValue(value); + } + + /** + * Gets the current value in the table, setting it if it does not exist. + * @param key the key + * @param defaultValue the default value to set if key doesn't exist. + * @returns False if the table key exists with a different type + */ + boolean setDefaultValue(String key, NetworkTableValue defaultValue) { + return getEntry(key).setDefaultValue(defaultValue); + } + + /** + * Gets the value associated with a key as an object + * + * @param key the key of the value to look up + * @return the value associated with the given key, or nullptr if the key + * does not exist + */ + NetworkTableValue getValue(String key) { + return getEntry(key).getValue(); + } + + /** + * {@inheritDoc} + */ + public String getPath() { + return path; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof NetworkTable)) { + return false; + } + NetworkTable other = (NetworkTable) o; + return inst.equals(other.inst) && path.equals(other.path); + } + + @Override + public int hashCode() { + return Objects.hash(inst, path); + } +} diff --git a/src/main/java/edu/wpi/first/networktables/NetworkTableEntry.java b/src/main/java/edu/wpi/first/networktables/NetworkTableEntry.java new file mode 100644 index 0000000..cfba495 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/NetworkTableEntry.java @@ -0,0 +1,709 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +import java.nio.ByteBuffer; +import java.util.function.Consumer; + +/** + * NetworkTables Entry + */ +public final class NetworkTableEntry { + /** + * Flag values (as returned by {@link #getFlags()}). + */ + public static final int kPersistent = 0x01; + + /** + * Construct from native handle. + * @param inst Instance + * @param handle Native handle + */ + public NetworkTableEntry(NetworkTableInstance inst, int handle) { + m_inst = inst; + m_handle = handle; + } + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + public boolean isValid() { + return m_handle != 0; + } + + /** + * Gets the native handle for the entry. + * @return Native handle + */ + public int getHandle() { + return m_handle; + } + + /** + * Gets the instance for the entry. + * @return Instance + */ + public NetworkTableInstance getInstance() { + return m_inst; + } + + /** + * Determines if the entry currently exists. + * @return True if the entry exists, false otherwise. + */ + public boolean exists() { + return NetworkTablesJNI.getType(m_handle) != 0; + } + + /** + * Gets the name of the entry (the key). + * @return the entry's name + */ + public String getName() { + return NetworkTablesJNI.getEntryName(m_handle); + } + + /** + * Gets the type of the entry. + * @return the entry's type + */ + public NetworkTableType getType() { + return NetworkTableType.getFromInt(NetworkTablesJNI.getType(m_handle)); + } + + /** + * Returns the flags. + * @return the flags (bitmask) + */ + public int getFlags() { + return NetworkTablesJNI.getEntryFlags(m_handle); + } + + /** + * Gets the last time the entry's value was changed. + * @return Entry last change time + */ + public long getLastChange() { + return NetworkTablesJNI.getEntryLastChange(m_handle); + } + + /** + * Gets combined information about the entry. + * @return Entry information + */ + public EntryInfo getInfo() { + return NetworkTablesJNI.getEntryInfoHandle(m_inst, m_handle); + } + + /** + * Gets the entry's value. + * Returns a value with type NetworkTableType.kUnassigned if the value + * does not exist. + * @return the entry's value + */ + public NetworkTableValue getValue() { + return NetworkTablesJNI.getValue(m_handle); + } + + /** + * Gets the entry's value as a boolean. If the entry does not exist or is of + * different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public boolean getBoolean(boolean defaultValue) { + return NetworkTablesJNI.getBoolean(m_handle, defaultValue); + } + + /** + * Gets the entry's value as a double. If the entry does not exist or is of + * different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public double getDouble(double defaultValue) { + return NetworkTablesJNI.getDouble(m_handle, defaultValue); + } + + /** + * Gets the entry's value as a double. If the entry does not exist or is of + * different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public Number getNumber(Number defaultValue) { + return NetworkTablesJNI.getDouble(m_handle, defaultValue.doubleValue()); + } + + /** + * Gets the entry's value as a string. If the entry does not exist or is of + * different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public String getString(String defaultValue) { + return NetworkTablesJNI.getString(m_handle, defaultValue); + } + + /** + * Gets the entry's value as a raw value (byte array). If the entry does not + * exist or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public byte[] getRaw(byte[] defaultValue) { + return NetworkTablesJNI.getRaw(m_handle, defaultValue); + } + + /** + * Gets the entry's value as a boolean array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public boolean[] getBooleanArray(boolean[] defaultValue) { + return NetworkTablesJNI.getBooleanArray(m_handle, defaultValue); + } + + /** + * Gets the entry's value as a boolean array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public Boolean[] getBooleanArray(Boolean[] defaultValue) { + return NetworkTableValue.fromNative(NetworkTablesJNI.getBooleanArray(m_handle, NetworkTableValue.toNative(defaultValue))); + } + + /** + * Gets the entry's value as a double array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public double[] getDoubleArray(double[] defaultValue) { + return NetworkTablesJNI.getDoubleArray(m_handle, defaultValue); + } + + /** + * Gets the entry's value as a double array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public Double[] getDoubleArray(Double[] defaultValue) { + return NetworkTableValue.fromNative(NetworkTablesJNI.getDoubleArray(m_handle, NetworkTableValue.toNative(defaultValue))); + } + + /** + * Gets the entry's value as a double array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public Number[] getNumberArray(Number[] defaultValue) { + return NetworkTableValue.fromNative(NetworkTablesJNI.getDoubleArray(m_handle, NetworkTableValue.toNative(defaultValue))); + } + + /** + * Gets the entry's value as a string array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + public String[] getStringArray(String[] defaultValue) { + return NetworkTablesJNI.getStringArray(m_handle, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultValue(NetworkTableValue defaultValue) { + long time = defaultValue.getTime(); + Object o = defaultValue.getValue(); + switch (defaultValue.getType()) { + case kBoolean: + return NetworkTablesJNI.setDefaultBoolean(m_handle, time, ((Boolean)o).booleanValue()); + case kDouble: + return NetworkTablesJNI.setDefaultDouble(m_handle, time, ((Number)o).doubleValue()); + case kString: + return NetworkTablesJNI.setDefaultString(m_handle, time, (String)o); + case kRaw: + return NetworkTablesJNI.setDefaultRaw(m_handle, time, (byte[])o); + case kBooleanArray: + return NetworkTablesJNI.setDefaultBooleanArray(m_handle, time, (boolean[])o); + case kDoubleArray: + return NetworkTablesJNI.setDefaultDoubleArray(m_handle, time, (double[])o); + case kStringArray: + return NetworkTablesJNI.setDefaultStringArray(m_handle, time, (String[])o); + case kRpc: + // TODO + default: + return true; + } + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultBoolean(boolean defaultValue) { + return NetworkTablesJNI.setDefaultBoolean(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultDouble(double defaultValue) { + return NetworkTablesJNI.setDefaultDouble(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultNumber(Number defaultValue) { + return NetworkTablesJNI.setDefaultDouble(m_handle, 0, defaultValue.doubleValue()); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultString(String defaultValue) { + return NetworkTablesJNI.setDefaultString(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultRaw(byte[] defaultValue) { + return NetworkTablesJNI.setDefaultRaw(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultBooleanArray(boolean[] defaultValue) { + return NetworkTablesJNI.setDefaultBooleanArray(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultBooleanArray(Boolean[] defaultValue) { + return NetworkTablesJNI.setDefaultBooleanArray(m_handle, 0, NetworkTableValue.toNative(defaultValue)); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultDoubleArray(double[] defaultValue) { + return NetworkTablesJNI.setDefaultDoubleArray(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultNumberArray(Number[] defaultValue) { + return NetworkTablesJNI.setDefaultDoubleArray(m_handle, 0, NetworkTableValue.toNative(defaultValue)); + } + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + public boolean setDefaultStringArray(String[] defaultValue) { + return NetworkTablesJNI.setDefaultStringArray(m_handle, 0, defaultValue); + } + + /** + * Sets the entry's value + * @param value the value that will be assigned + * @return False if the table key already exists with a different type + */ + public boolean setValue(NetworkTableValue value) { + long time = value.getTime(); + Object o = value.getValue(); + switch (value.getType()) { + case kBoolean: + return NetworkTablesJNI.setBoolean(m_handle, time, ((Boolean)o).booleanValue(), false); + case kDouble: + return NetworkTablesJNI.setDouble(m_handle, time, ((Number)o).doubleValue(), false); + case kString: + return NetworkTablesJNI.setString(m_handle, time, (String)o, false); + case kRaw: + return NetworkTablesJNI.setRaw(m_handle, time, (byte[])o, false); + case kBooleanArray: + return NetworkTablesJNI.setBooleanArray(m_handle, time, (boolean[])o, false); + case kDoubleArray: + return NetworkTablesJNI.setDoubleArray(m_handle, time, (double[])o, false); + case kStringArray: + return NetworkTablesJNI.setStringArray(m_handle, time, (String[])o, false); + case kRpc: + // TODO + default: + return true; + } + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setBoolean(boolean value) { + return NetworkTablesJNI.setBoolean(m_handle, 0, value, false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setDouble(double value) { + return NetworkTablesJNI.setDouble(m_handle, 0, value, false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setNumber(Number value) { + return NetworkTablesJNI.setDouble(m_handle, 0, value.doubleValue(), false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setString(String value) { + return NetworkTablesJNI.setString(m_handle, 0, value, false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setRaw(byte[] value) { + return NetworkTablesJNI.setRaw(m_handle, 0, value, false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @param len the length of the value + * @return False if the entry exists with a different type + */ + public boolean setRaw(ByteBuffer value, int len) { + if (!value.isDirect()) + throw new IllegalArgumentException("must be a direct buffer"); + if (value.capacity() < len) + throw new IllegalArgumentException("buffer is too small, must be at least " + len); + return NetworkTablesJNI.setRaw(m_handle, 0, value, len, false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setBooleanArray(boolean[] value) { + return NetworkTablesJNI.setBooleanArray(m_handle, 0, value, false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setBooleanArray(Boolean[] value) { + return NetworkTablesJNI.setBooleanArray(m_handle, 0, NetworkTableValue.toNative(value), false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setDoubleArray(double[] value) { + return NetworkTablesJNI.setDoubleArray(m_handle, 0, value, false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setNumberArray(Number[] value) { + return NetworkTablesJNI.setDoubleArray(m_handle, 0, NetworkTableValue.toNative(value), false); + } + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + public boolean setStringArray(String[] value) { + return NetworkTablesJNI.setStringArray(m_handle, 0, value, false); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetValue(NetworkTableValue value) { + long time = value.getTime(); + Object o = value.getValue(); + switch (value.getType()) { + case kBoolean: + NetworkTablesJNI.setBoolean(m_handle, time, ((Boolean)o).booleanValue(), true); + return; + case kDouble: + NetworkTablesJNI.setDouble(m_handle, time, ((Number)o).doubleValue(), true); + return; + case kString: + NetworkTablesJNI.setString(m_handle, time, (String)o, true); + return; + case kRaw: + NetworkTablesJNI.setRaw(m_handle, time, (byte[])o, true); + return; + case kBooleanArray: + NetworkTablesJNI.setBooleanArray(m_handle, time, (boolean[])o, true); + return; + case kDoubleArray: + NetworkTablesJNI.setDoubleArray(m_handle, time, (double[])o, true); + return; + case kStringArray: + NetworkTablesJNI.setStringArray(m_handle, time, (String[])o, true); + return; + case kRpc: + // TODO + default: + return; + } + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetBoolean(boolean value) { + NetworkTablesJNI.setBoolean(m_handle, 0, value, true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetDouble(double value) { + NetworkTablesJNI.setDouble(m_handle, 0, value, true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetNumber(Number value) { + NetworkTablesJNI.setDouble(m_handle, 0, value.doubleValue(), true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetString(String value) { + NetworkTablesJNI.setString(m_handle, 0, value, true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetRaw(byte[] value) { + NetworkTablesJNI.setRaw(m_handle, 0, value, true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetBooleanArray(boolean[] value) { + NetworkTablesJNI.setBooleanArray(m_handle, 0, value, true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetBooleanArray(Boolean[] value) { + NetworkTablesJNI.setBooleanArray(m_handle, 0, NetworkTableValue.toNative(value), true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetDoubleArray(double[] value) { + NetworkTablesJNI.setDoubleArray(m_handle, 0, value, true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetNumberArray(Number[] value) { + NetworkTablesJNI.setDoubleArray(m_handle, 0, NetworkTableValue.toNative(value), true); + } + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + public void forceSetStringArray(String[] value) { + NetworkTablesJNI.setStringArray(m_handle, 0, value, true); + } + + /** + * Sets flags. + * @param flags the flags to set (bitmask) + */ + public void setFlags(int flags) { + NetworkTablesJNI.setEntryFlags(m_handle, getFlags() | flags); + } + + /** + * Clears flags. + * @param flags the flags to clear (bitmask) + */ + public void clearFlags(int flags) { + NetworkTablesJNI.setEntryFlags(m_handle, getFlags() & ~flags); + } + + /** + * Make value persistent through program restarts. + */ + public void setPersistent() { + setFlags(kPersistent); + } + + /** + * Stop making value persistent through program restarts. + */ + public void clearPersistent() { + clearFlags(kPersistent); + } + + /** + * Returns whether the value is persistent through program restarts. + * @return True if the value is persistent. + */ + public boolean isPersistent() { + return (getFlags() & kPersistent) != 0; + } + + /** + * Deletes the entry. + */ + public void delete() { + NetworkTablesJNI.deleteEntry(m_handle); + } + + /** + * Create a callback-based RPC entry point. Only valid to use on the server. + * The callback function will be called when the RPC is called. + * This function creates RPC version 0 definitions (raw data in and out). + * @param callback callback function + */ + void createRpc(Consumer callback) { + m_inst.createRpc(this, callback); + } + + /** + * Call a RPC function. May be used on either the client or server. + * This function is non-blocking. Either {@link RpcCall#GetResult()} or + * {@link RpcCall#CancelResult()} must be called on the return value to either + * get or ignore the result of the call. + * @param params parameter + * @return RPC call object. + */ + RpcCall callRpc(byte[] params) { + return new RpcCall(this, NetworkTablesJNI.callRpc(m_handle, params)); + } + + /** + * Add a listener for changes to the entry + * @param listener the listener to add + * @param flags bitmask specifying desired notifications + * @return listener handle + */ + public int addListener(Consumer listener, int flags) { + return m_inst.addEntryListener(this, listener, flags); + } + + /** + * Remove a listener from receiving entry events + * @param listener the listener to be removed + */ + public void removeListener(int listener) { + m_inst.removeEntryListener(listener); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof NetworkTableEntry)) { + return false; + } + NetworkTableEntry other = (NetworkTableEntry) o; + return m_handle == other.m_handle; + } + + @Override + public int hashCode() { + return m_handle; + } + + private NetworkTableInstance m_inst; + private int m_handle; +} diff --git a/src/main/java/edu/wpi/first/networktables/NetworkTableInstance.java b/src/main/java/edu/wpi/first/networktables/NetworkTableInstance.java new file mode 100644 index 0000000..5e337cd --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/NetworkTableInstance.java @@ -0,0 +1,1082 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +import java.util.HashMap; +import java.util.Map; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.locks.Condition; +import java.util.concurrent.locks.ReentrantLock; +import java.util.function.Consumer; + +/** + * NetworkTables Instance. + * + * Instances are completely independent from each other. Table operations on + * one instance will not be visible to other instances unless the instances are + * connected via the network. The main limitation on instances is that you + * cannot have two servers on the same network port. The main utility of + * instances is for unit testing, but they can also enable one program to + * connect to two different NetworkTables networks. + * + * The global "default" instance (as returned by {@link #getDefault()}) is + * always available, and is intended for the common case when there is only + * a single NetworkTables instance being used in the program. + * + * Additional instances can be created with the {@link #create()} function. + * A reference must be kept to the NetworkTableInstance returned by this + * function to keep it from being garbage collected. + */ +public final class NetworkTableInstance { + /** + * Client/server mode flag values (as returned by {@link #getNetworkMode()}). + * This is a bitmask. + */ + public static final int kNetModeNone = 0x00; + public static final int kNetModeServer = 0x01; + public static final int kNetModeClient = 0x02; + public static final int kNetModeStarting = 0x04; + public static final int kNetModeFailure = 0x08; + + /** + * The default port that network tables operates on. + */ + public static final int kDefaultPort = 1735; + + /** + * Construct from native handle. + * @param handle Native handle + */ + private NetworkTableInstance(int handle) { + m_owned = false; + m_handle = handle; + } + + /** + * Destroys the instance (if created by {@link #create()}). + */ + public synchronized void free() { + if (m_owned && m_handle != 0) { + NetworkTablesJNI.destroyInstance(m_handle); + } + } + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + public boolean isValid() { + return m_handle != 0; + } + + /* The default instance. */ + private static NetworkTableInstance s_defaultInstance; + + /** + * Get global default instance. + * @return Global default instance + */ + public synchronized static NetworkTableInstance getDefault() { + if (s_defaultInstance == null) { + s_defaultInstance = new NetworkTableInstance(NetworkTablesJNI.getDefaultInstance()); + } + return s_defaultInstance; + } + + /** + * Create an instance. + * Note: A reference to the returned instance must be retained to ensure the + * instance is not garbage collected. + * @return Newly created instance + */ + public static NetworkTableInstance create() { + NetworkTableInstance inst = new NetworkTableInstance(NetworkTablesJNI.createInstance()); + inst.m_owned = true; + return inst; + } + + /** + * Gets the native handle for the entry. + * @return Native handle + */ + public int getHandle() { + return m_handle; + } + + /** + * Gets the entry for a key. + * @param name Key + * @return Network table entry. + */ + public NetworkTableEntry getEntry(String name) { + return new NetworkTableEntry(this, NetworkTablesJNI.getEntry(m_handle, name)); + } + + /** + * Get entries starting with the given prefix. + * The results are optionally filtered by string prefix and entry type to + * only return a subset of all entries. + * + * @param prefix entry name required prefix; only entries whose name + * starts with this string are returned + * @param types bitmask of types; 0 is treated as a "don't care" + * @return Array of entries. + */ + public NetworkTableEntry[] getEntries(String prefix, int types) { + int[] handles = NetworkTablesJNI.getEntries(m_handle, prefix, types); + NetworkTableEntry[] entries = new NetworkTableEntry[handles.length]; + for (int i = 0; i < handles.length; i++) { + entries[i] = new NetworkTableEntry(this, handles[i]); + } + return entries; + } + + /** + * Get information about entries starting with the given prefix. + * The results are optionally filtered by string prefix and entry type to + * only return a subset of all entries. + * + * @param prefix entry name required prefix; only entries whose name + * starts with this string are returned + * @param types bitmask of types; 0 is treated as a "don't care" + * @return Array of entry information. + */ + public EntryInfo[] getEntryInfo(String prefix, int types) { + return NetworkTablesJNI.getEntryInfo(this, m_handle, prefix, types); + } + + /* Cache of created tables. */ + private final ConcurrentMap m_tables = new ConcurrentHashMap<>(); + + /** + * Gets the table with the specified key. + * + * @param key the key name + * @return The network table + */ + public NetworkTable getTable(String key) { + // prepend leading / if not present + String theKey; + if (key.isEmpty() || key.equals("/")) { + theKey = ""; + } else if (key.charAt(0) == NetworkTable.PATH_SEPARATOR) { + theKey = key; + } else { + theKey = NetworkTable.PATH_SEPARATOR + key; + } + + // cache created tables + NetworkTable table = m_tables.get(theKey); + if (table == null) { + table = new NetworkTable(this, theKey); + NetworkTable oldTable = m_tables.putIfAbsent(theKey, table); + if (oldTable != null) { + table = oldTable; + } + } + return table; + } + + /** + * Deletes ALL keys in ALL subtables (except persistent values). + * Use with caution! + */ + public void deleteAllEntries() { + NetworkTablesJNI.deleteAllEntries(m_handle); + } + + /* + * Callback Creation Functions + */ + + private static class EntryConsumer { + final NetworkTableEntry entry; + final Consumer consumer; + + EntryConsumer(NetworkTableEntry entry, Consumer consumer) { + this.entry = entry; + this.consumer = consumer; + } + } + + private final ReentrantLock m_entryListenerLock = new ReentrantLock(); + private final Map> m_entryListeners = new HashMap<>(); + private Thread m_entryListenerThread; + private int m_entryListenerPoller; + private boolean m_entryListenerWaitQueue; + private final Condition m_entryListenerWaitQueueCond = m_entryListenerLock.newCondition(); + + private void startEntryListenerThread() { + m_entryListenerThread = new Thread(() -> { + boolean wasInterrupted = false; + while (!Thread.interrupted()) { + EntryNotification[] events; + try { + events = NetworkTablesJNI.pollEntryListener(this, m_entryListenerPoller); + } catch (InterruptedException ex) { + m_entryListenerLock.lock(); + try { + if (m_entryListenerWaitQueue) { + m_entryListenerWaitQueue = false; + m_entryListenerWaitQueueCond.signalAll(); + continue; + } + } finally { + m_entryListenerLock.unlock(); + } + Thread.currentThread().interrupt(); + wasInterrupted = true; // don't try to destroy poller, as its handle is likely no longer valid + break; + } + for (EntryNotification event : events) { + EntryConsumer listener; + m_entryListenerLock.lock(); + try { + listener = m_entryListeners.get(event.listener); + } finally { + m_entryListenerLock.unlock(); + } + if (listener != null) { + event.entryObject = listener.entry; + try { + listener.consumer.accept(event); + } catch (Throwable throwable) { + System.err.println("Unhandled exception during entry listener callback: " + throwable.toString()); + throwable.printStackTrace(); + } + } + } + } + m_entryListenerLock.lock(); + try { + if (!wasInterrupted) { + NetworkTablesJNI.destroyEntryListenerPoller(m_entryListenerPoller); + } + m_entryListenerPoller = 0; + } finally { + m_entryListenerLock.unlock(); + } + }, "NTEntryListener"); + m_entryListenerThread.setDaemon(true); + m_entryListenerThread.start(); + } + + /** + * Add a listener for all entries starting with a certain prefix. + * + * @param prefix UTF-8 string prefix + * @param listener listener to add + * @param flags {@link EntryListenerFlags} bitmask + * @return Listener handle + */ + public int addEntryListener(String prefix, Consumer listener, int flags) { + m_entryListenerLock.lock(); + try { + if (m_entryListenerPoller == 0) { + m_entryListenerPoller = NetworkTablesJNI.createEntryListenerPoller(m_handle); + startEntryListenerThread(); + } + int handle = NetworkTablesJNI.addPolledEntryListener(m_entryListenerPoller, prefix, flags); + m_entryListeners.put(handle, new EntryConsumer<>(null, listener)); + return handle; + } finally { + m_entryListenerLock.unlock(); + } + } + + /** + * Add a listener for a particular entry. + * + * @param entry the entry + * @param listener listener to add + * @param flags {@link EntryListenerFlags} bitmask + * @return Listener handle + */ + public int addEntryListener(NetworkTableEntry entry, Consumer listener, int flags) { + if (!equals(entry.getInstance())) { + throw new IllegalArgumentException("entry does not belong to this instance"); + } + m_entryListenerLock.lock(); + try { + if (m_entryListenerPoller == 0) { + m_entryListenerPoller = NetworkTablesJNI.createEntryListenerPoller(m_handle); + startEntryListenerThread(); + } + int handle = NetworkTablesJNI.addPolledEntryListener(m_entryListenerPoller, entry.getHandle(), flags); + m_entryListeners.put(handle, new EntryConsumer<>(entry, listener)); + return handle; + } finally { + m_entryListenerLock.unlock(); + } + } + + /** + * Remove an entry listener. + * @param listener Listener handle to remove + */ + public void removeEntryListener(int listener) { + NetworkTablesJNI.removeEntryListener(listener); + } + + /** + * Wait for the entry listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the entry listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + boolean waitForEntryListenerQueue(double timeout) { + if (!NetworkTablesJNI.waitForEntryListenerQueue(m_handle, timeout)) { + return false; + } + m_entryListenerLock.lock(); + try { + if (m_entryListenerPoller != 0) { + m_entryListenerWaitQueue = true; + NetworkTablesJNI.cancelPollEntryListener(m_entryListenerPoller); + while (m_entryListenerWaitQueue) { + try { + return m_entryListenerWaitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return true; + } + } + } + } finally { + m_entryListenerLock.unlock(); + } + return true; + } + + private final ReentrantLock m_connectionListenerLock = new ReentrantLock(); + private final Map> m_connectionListeners = new HashMap<>(); + private Thread m_connectionListenerThread; + private int m_connectionListenerPoller; + private boolean m_connectionListenerWaitQueue; + private final Condition m_connectionListenerWaitQueueCond = m_connectionListenerLock.newCondition(); + + private void startConnectionListenerThread() { + m_connectionListenerThread = new Thread(() -> { + boolean wasInterrupted = false; + while (!Thread.interrupted()) { + ConnectionNotification[] events; + try { + events = NetworkTablesJNI.pollConnectionListener(this, m_connectionListenerPoller); + } catch (InterruptedException ex) { + m_connectionListenerLock.lock(); + try { + if (m_connectionListenerWaitQueue) { + m_connectionListenerWaitQueue = false; + m_connectionListenerWaitQueueCond.signalAll(); + continue; + } + } finally { + m_connectionListenerLock.unlock(); + } + Thread.currentThread().interrupt(); + wasInterrupted = true; // don't try to destroy poller, as its handle is likely no longer valid + break; + } + for (ConnectionNotification event : events) { + Consumer listener; + m_connectionListenerLock.lock(); + try { + listener = m_connectionListeners.get(event.listener); + } finally { + m_connectionListenerLock.unlock(); + } + if (listener != null) { + try { + listener.accept(event); + } catch (Throwable throwable) { + System.err.println("Unhandled exception during connection listener callback: " + throwable.toString()); + throwable.printStackTrace(); + } + } + } + } + m_connectionListenerLock.lock(); + try { + if (!wasInterrupted) { + NetworkTablesJNI.destroyConnectionListenerPoller(m_connectionListenerPoller); + } + m_connectionListenerPoller = 0; + } finally { + m_connectionListenerLock.unlock(); + } + }, "NTConnectionListener"); + m_connectionListenerThread.setDaemon(true); + m_connectionListenerThread.start(); + } + + /** + * Add a connection listener. + * + * @param listener Listener to add + * @param immediateNotify Notify listener of all existing connections + * @return Listener handle + */ + public int addConnectionListener(Consumer listener, boolean immediateNotify) { + m_connectionListenerLock.lock(); + try { + if (m_connectionListenerPoller == 0) { + m_connectionListenerPoller = NetworkTablesJNI.createConnectionListenerPoller(m_handle); + startConnectionListenerThread(); + } + int handle = NetworkTablesJNI.addPolledConnectionListener(m_connectionListenerPoller, immediateNotify); + m_connectionListeners.put(handle, listener); + return handle; + } finally { + m_connectionListenerLock.unlock(); + } + } + + /** + * Remove a connection listener. + * @param listener Listener handle to remove + */ + public void removeConnectionListener(int listener) { + m_connectionListenerLock.lock(); + try { + m_connectionListeners.remove(listener); + } finally { + m_connectionListenerLock.unlock(); + } + NetworkTablesJNI.removeConnectionListener(listener); + } + + /** + * Wait for the connection listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the connection listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + boolean waitForConnectionListenerQueue(double timeout) { + if (!NetworkTablesJNI.waitForConnectionListenerQueue(m_handle, timeout)) { + return false; + } + m_connectionListenerLock.lock(); + try { + if (m_connectionListenerPoller != 0) { + m_connectionListenerWaitQueue = true; + NetworkTablesJNI.cancelPollConnectionListener(m_connectionListenerPoller); + while (m_connectionListenerWaitQueue) { + try { + return m_connectionListenerWaitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return true; + } + } + } + } finally { + m_connectionListenerLock.unlock(); + } + return true; + } + + /* + * Remote Procedure Call Functions + */ + + private final ReentrantLock m_rpcCallLock = new ReentrantLock(); + private final Map> m_rpcCalls = new HashMap<>(); + private Thread m_rpcCallThread; + private int m_rpcCallPoller; + private boolean m_rpcCallWaitQueue; + private final Condition m_rpcCallWaitQueueCond = m_rpcCallLock.newCondition(); + + private void startRpcCallThread() { + m_rpcCallThread = new Thread(() -> { + boolean wasInterrupted = false; + while (!Thread.interrupted()) { + RpcAnswer[] events; + try { + events = NetworkTablesJNI.pollRpc(this, m_rpcCallPoller); + } catch (InterruptedException ex) { + m_rpcCallLock.lock(); + try { + if (m_rpcCallWaitQueue) { + m_rpcCallWaitQueue = false; + m_rpcCallWaitQueueCond.signalAll(); + continue; + } + } finally { + m_rpcCallLock.unlock(); + } + Thread.currentThread().interrupt(); + wasInterrupted = true; // don't try to destroy poller, as its handle is likely no longer valid + break; + } + for (RpcAnswer event : events) { + EntryConsumer listener; + m_rpcCallLock.lock(); + try { + listener = m_rpcCalls.get(event.entry); + } finally { + m_rpcCallLock.unlock(); + } + if (listener != null) { + event.entryObject = listener.entry; + try { + listener.consumer.accept(event); + } catch (Throwable throwable) { + System.err.println("Unhandled exception during RPC callback: " + throwable.toString()); + throwable.printStackTrace(); + } + } + } + } + m_rpcCallLock.lock(); + try { + if (!wasInterrupted) { + NetworkTablesJNI.destroyRpcCallPoller(m_rpcCallPoller); + } + m_rpcCallPoller = 0; + } finally { + m_rpcCallLock.unlock(); + } + }, "NTRpcCall"); + m_rpcCallThread.setDaemon(true); + m_rpcCallThread.start(); + } + + private static final byte[] rev0def = new byte[] {0}; + + /** + * Create a callback-based RPC entry point. Only valid to use on the server. + * The callback function will be called when the RPC is called. + * This function creates RPC version 0 definitions (raw data in and out). + * @param entry the entry + * @param callback callback function + */ + public void createRpc(NetworkTableEntry entry, Consumer callback) { + m_rpcCallLock.lock(); + try { + if (m_rpcCallPoller == 0) { + m_rpcCallPoller = NetworkTablesJNI.createRpcCallPoller(m_handle); + startRpcCallThread(); + } + NetworkTablesJNI.createPolledRpc(entry.getHandle(), rev0def, m_rpcCallPoller); + m_rpcCalls.put(entry.getHandle(), new EntryConsumer<>(entry, callback)); + } finally { + m_rpcCallLock.unlock(); + } + } + + /** + * Wait for the incoming RPC call queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the RPC call + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + boolean waitForRpcCallQueue(double timeout) { + if (!NetworkTablesJNI.waitForRpcCallQueue(m_handle, timeout)) { + return false; + } + m_rpcCallLock.lock(); + try { + if (m_rpcCallPoller != 0) { + m_rpcCallWaitQueue = true; + NetworkTablesJNI.cancelPollRpc(m_rpcCallPoller); + while (m_rpcCallWaitQueue) { + try { + return m_rpcCallWaitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return true; + } + } + } + } finally { + m_rpcCallLock.unlock(); + } + return true; + } + + /* + * Client/Server Functions + */ + + /** + * Set the network identity of this node. + * This is the name used during the initial connection handshake, and is + * visible through ConnectionInfo on the remote node. + * @param name identity to advertise + */ + public void setNetworkIdentity(String name) { + NetworkTablesJNI.setNetworkIdentity(m_handle, name); + } + + /** + * Get the current network mode. + * @return Bitmask of NetworkMode. + */ + public int getNetworkMode() { + return NetworkTablesJNI.getNetworkMode(m_handle); + } + + /** + * Starts a server using the networktables.ini as the persistent file, + * using the default listening address and port. + */ + public void startServer() { + startServer("networktables.ini"); + } + + /** + * Starts a server using the specified persistent filename, using the default + * listening address and port. + * + * @param persistFilename the name of the persist file to use + */ + public void startServer(String persistFilename) { + startServer(persistFilename, ""); + } + + /** + * Starts a server using the specified filename and listening address, + * using the default port. + * + * @param persistFilename the name of the persist file to use + * @param listenAddress the address to listen on, or empty to listen on any + * address + */ + public void startServer(String persistFilename, String listenAddress) { + startServer(persistFilename, listenAddress, kDefaultPort); + } + + /** + * Starts a server using the specified filename, listening address, and port. + * + * @param persistFilename the name of the persist file to use + * @param listenAddress the address to listen on, or empty to listen on any + * address + * @param port port to communicate over + */ + public void startServer(String persistFilename, String listenAddress, int port) { + NetworkTablesJNI.startServer(m_handle, persistFilename, listenAddress, port); + } + + /** + * Stops the server if it is running. + */ + public void stopServer() { + NetworkTablesJNI.stopServer(m_handle); + } + + /** + * Starts a client. Use SetServer to set the server name and port. + */ + public void startClient() { + NetworkTablesJNI.startClient(m_handle); + } + + /** + * Starts a client using the specified server and the default port + * + * @param serverName server name + */ + public void startClient(String serverName) { + startClient(serverName, kDefaultPort); + } + + /** + * Starts a client using the specified server and port + * + * @param serverName server name + * @param port port to communicate over + */ + public void startClient(String serverName, int port) { + NetworkTablesJNI.startClient(m_handle, serverName, port); + } + + /** + * Starts a client using the specified servers and default port. The + * client will attempt to connect to each server in round robin fashion. + * + * @param serverNames array of server names + */ + public void startClient(String[] serverNames) { + startClient(serverNames, kDefaultPort); + } + + /** + * Starts a client using the specified servers and port number. The + * client will attempt to connect to each server in round robin fashion. + * + * @param serverNames array of server names + * @param port port to communicate over + */ + public void startClient(String[] serverNames, int port) { + int[] ports = new int[serverNames.length]; + for (int i = 0; i < serverNames.length; i++) { + ports[i] = port; + } + startClient(serverNames, ports); + } + + /** + * Starts a client using the specified (server, port) combinations. The + * client will attempt to connect to each server in round robin fashion. + * + * @param serverNames array of server names + * @param ports array of port numbers + */ + public void startClient(String[] serverNames, int[] ports) { + NetworkTablesJNI.startClient(m_handle, serverNames, ports); + } + + /** + * Starts a client using commonly known robot addresses for the specified + * team using the default port number. + * + * @param team team number + */ + public void startClientTeam(int team) { + startClientTeam(team, kDefaultPort); + } + + /** + * Starts a client using commonly known robot addresses for the specified + * team. + * + * @param team team number + * @param port port to communicate over + */ + public void startClientTeam(int team, int port) { + NetworkTablesJNI.startClientTeam(m_handle, team, port); + } + + /** + * Stops the client if it is running. + */ + public void stopClient() { + NetworkTablesJNI.stopClient(m_handle); + } + + /** + * Sets server address and port for client (without restarting client). + * Changes the port to the default port. + * + * @param serverName server name + */ + public void setServer(String serverName) { + setServer(serverName, kDefaultPort); + } + + /** + * Sets server address and port for client (without restarting client). + * + * @param serverName server name + * @param port port to communicate over + */ + public void setServer(String serverName, int port) { + NetworkTablesJNI.setServer(m_handle, serverName, port); + } + + /** + * Sets server addresses and port for client (without restarting client). + * Changes the port to the default port. The client will attempt to connect + * to each server in round robin fashion. + * + * @param serverNames array of server names + */ + public void setServer(String[] serverNames) { + setServer(serverNames, kDefaultPort); + } + + /** + * Sets server addresses and port for client (without restarting client). + * The client will attempt to connect to each server in round robin fashion. + * + * @param serverNames array of server names + * @param port port to communicate over + */ + public void setServer(String[] serverNames, int port) { + int[] ports = new int[serverNames.length]; + for (int i = 0; i < serverNames.length; i++) { + ports[i] = port; + } + setServer(serverNames, ports); + } + + /** + * Sets server addresses and ports for client (without restarting client). + * The client will attempt to connect to each server in round robin fashion. + * + * @param serverNames array of server names + * @param ports array of port numbers + */ + public void setServer(String[] serverNames, int[] ports) { + NetworkTablesJNI.setServer(m_handle, serverNames, ports); + } + + /** + * Sets server addresses and port for client (without restarting client). + * Changes the port to the default port. The client will attempt to connect + * to each server in round robin fashion. + * + * @param team team number + */ + public void setServerTeam(int team) { + setServerTeam(team, kDefaultPort); + } + + /** + * Sets server addresses and port for client (without restarting client). + * Connects using commonly known robot addresses for the specified team. + * + * @param team team number + * @param port port to communicate over + */ + public void setServerTeam(int team, int port) { + NetworkTablesJNI.setServerTeam(m_handle, team, port); + } + + /** + * Starts requesting server address from Driver Station. + * This connects to the Driver Station running on localhost to obtain the + * server IP address, and connects with the default port. + */ + public void startDSClient() { + startDSClient(kDefaultPort); + } + + /** + * Starts requesting server address from Driver Station. + * This connects to the Driver Station running on localhost to obtain the + * server IP address. + * + * @param port server port to use in combination with IP from DS + */ + public void startDSClient(int port) { + NetworkTablesJNI.startDSClient(m_handle, port); + } + + /** + * Stops requesting server address from Driver Station. + */ + public void stopDSClient() { + NetworkTablesJNI.stopDSClient(m_handle); + } + + /** + * Set the periodic update rate. + * Sets how frequently updates are sent to other nodes over the network. + * + * @param interval update interval in seconds (range 0.01 to 1.0) + */ + public void setUpdateRate(double interval) { + NetworkTablesJNI.setUpdateRate(m_handle, interval); + } + + /** + * Flushes all updated values immediately to the network. + * Note: This is rate-limited to protect the network from flooding. + * This is primarily useful for synchronizing network updates with + * user code. + */ + public void flush() { + NetworkTablesJNI.flush(m_handle); + } + + /** + * Gets information on the currently established network connections. + * If operating as a client, this will return either zero or one values. + * @return array of connection information + */ + public ConnectionInfo[] getConnections() { + return NetworkTablesJNI.getConnections(m_handle); + } + + /** + * Return whether or not the instance is connected to another node. + * @return True if connected. + */ + public boolean isConnected() { + return NetworkTablesJNI.isConnected(m_handle); + } + + /** + * Saves persistent keys to a file. The server does this automatically. + * + * @param filename file name + * @throws PersistentException if error saving file + */ + public void savePersistent(String filename) throws PersistentException { + NetworkTablesJNI.savePersistent(m_handle, filename); + } + + /** + * Loads persistent keys from a file. The server does this automatically. + * + * @param filename file name + * @return List of warnings (errors result in an exception instead) + * @throws PersistentException if error reading file + */ + public String[] loadPersistent(String filename) throws PersistentException { + return NetworkTablesJNI.loadPersistent(m_handle, filename); + } + + private final ReentrantLock m_loggerLock = new ReentrantLock(); + private final Map> m_loggers = new HashMap<>(); + private Thread m_loggerThread; + private int m_loggerPoller; + private boolean m_loggerWaitQueue; + private final Condition m_loggerWaitQueueCond = m_loggerLock.newCondition(); + + private void startLogThread() { + m_loggerThread = new Thread(() -> { + boolean wasInterrupted = false; + while (!Thread.interrupted()) { + LogMessage[] events; + try { + events = NetworkTablesJNI.pollLogger(this, m_loggerPoller); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + wasInterrupted = true; // don't try to destroy poller, as its handle is likely no longer valid + break; + } + for (LogMessage event : events) { + Consumer logger; + m_loggerLock.lock(); + try { + logger = m_loggers.get(event.logger); + } finally { + m_loggerLock.unlock(); + } + if (logger != null) { + try { + logger.accept(event); + } catch (Throwable throwable) { + System.err.println("Unhandled exception during logger callback: " + throwable.toString()); + throwable.printStackTrace(); + } + } + } + } + m_loggerLock.lock(); + try { + if (!wasInterrupted) { + NetworkTablesJNI.destroyLoggerPoller(m_loggerPoller); + } + m_rpcCallPoller = 0; + } finally { + m_loggerLock.unlock(); + } + }, "NTLogger"); + m_loggerThread.setDaemon(true); + m_loggerThread.start(); + } + + /** + * Add logger callback function. By default, log messages are sent to stderr; + * this function sends log messages with the specified levels to the provided + * callback function instead. The callback function will only be called for + * log messages with level greater than or equal to minLevel and less than or + * equal to maxLevel; messages outside this range will be silently ignored. + * + * @param func log callback function + * @param minLevel minimum log level + * @param maxLevel maximum log level + * @return Logger handle + */ + public int addLogger(Consumer func, int minLevel, int maxLevel) { + m_loggerLock.lock(); + try { + if (m_loggerPoller == 0) { + m_loggerPoller = NetworkTablesJNI.createLoggerPoller(m_handle); + startLogThread(); + } + int handle = NetworkTablesJNI.addPolledLogger(m_loggerPoller, minLevel, maxLevel); + m_loggers.put(handle, func); + return handle; + } finally { + m_loggerLock.unlock(); + } + } + + /** + * Remove a logger. + * @param logger Logger handle to remove + */ + public void removeLogger(int logger) { + m_loggerLock.lock(); + try { + m_loggers.remove(logger); + } finally { + m_loggerLock.unlock(); + } + NetworkTablesJNI.removeLogger(logger); + } + + /** + * Wait for the incoming log event queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the log event + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + boolean waitForLoggerQueue(double timeout) { + if (!NetworkTablesJNI.waitForLoggerQueue(m_handle, timeout)) { + return false; + } + m_loggerLock.lock(); + try { + if (m_loggerPoller != 0) { + m_loggerWaitQueue = true; + NetworkTablesJNI.cancelPollLogger(m_loggerPoller); + while (m_loggerWaitQueue) { + try { + return m_loggerWaitQueueCond.await((long) (timeout * 1e9), TimeUnit.NANOSECONDS); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + return true; + } + } + } + } finally { + m_loggerLock.unlock(); + } + return true; + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof NetworkTableInstance)) { + return false; + } + NetworkTableInstance other = (NetworkTableInstance) o; + return m_handle == other.m_handle; + } + + @Override + public int hashCode() { + return m_handle; + } + + private boolean m_owned; + private int m_handle; +} diff --git a/src/main/java/edu/wpi/first/networktables/NetworkTableType.java b/src/main/java/edu/wpi/first/networktables/NetworkTableType.java new file mode 100644 index 0000000..7c82690 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/NetworkTableType.java @@ -0,0 +1,47 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * Network table data types. + */ +public enum NetworkTableType { + kUnassigned(0), + kBoolean(0x01), + kDouble(0x02), + kString(0x04), + kRaw(0x08), + kBooleanArray(0x10), + kDoubleArray(0x20), + kStringArray(0x40), + kRpc(0x80); + + private final int value; + + private NetworkTableType(int value) { + this.value = value; + } + + public int getValue() { + return value; + } + + public static NetworkTableType getFromInt(int value) { + switch (value) { + case 0x01: return kBoolean; + case 0x02: return kDouble; + case 0x04: return kString; + case 0x08: return kRaw; + case 0x10: return kBooleanArray; + case 0x20: return kDoubleArray; + case 0x40: return kStringArray; + case 0x80: return kRpc; + default: return kUnassigned; + } + } +} diff --git a/src/main/java/edu/wpi/first/networktables/NetworkTableValue.java b/src/main/java/edu/wpi/first/networktables/NetworkTableValue.java new file mode 100644 index 0000000..581856d --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/NetworkTableValue.java @@ -0,0 +1,472 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +import java.util.Objects; + +/** + * A network table entry value. + */ +public final class NetworkTableValue { + NetworkTableValue(NetworkTableType type, Object value, long time) { + m_type = type; + m_value = value; + m_time = time; + } + + NetworkTableValue(NetworkTableType type, Object value) { + this(type, value, NetworkTablesJNI.now()); + } + + NetworkTableValue(int type, Object value, long time) { + this(NetworkTableType.getFromInt(type), value, time); + } + + /** + * Get the data type. + * @return The type. + */ + public NetworkTableType getType() { + return m_type; + } + + /** + * Get the data value stored. + * @return The type. + */ + public Object getValue() { + return m_value; + } + + /** + * Get the creation time of the value. + * @return The time, in the units returned by NetworkTablesJNI.now(). + */ + public long getTime() { + return m_time; + } + + /* + * Type Checkers + */ + + /** + * Determine if entry value contains a value or is unassigned. + * @return True if the entry value contains a value. + */ + public boolean isValid() { + return m_type != NetworkTableType.kUnassigned; + } + + /** + * Determine if entry value contains a boolean. + * @return True if the entry value is of boolean type. + */ + public boolean isBoolean() { + return m_type == NetworkTableType.kBoolean; + } + + /** + * Determine if entry value contains a double. + * @return True if the entry value is of double type. + */ + public boolean isDouble() { + return m_type == NetworkTableType.kDouble; + } + + /** + * Determine if entry value contains a string. + * @return True if the entry value is of string type. + */ + public boolean isString() { + return m_type == NetworkTableType.kString; + } + + /** + * Determine if entry value contains a raw. + * @return True if the entry value is of raw type. + */ + public boolean isRaw() { + return m_type == NetworkTableType.kRaw; + } + + /** + * Determine if entry value contains a rpc definition. + * @return True if the entry value is of rpc definition type. + */ + public boolean isRpc() { + return m_type == NetworkTableType.kRpc; + } + + /** + * Determine if entry value contains a boolean array. + * @return True if the entry value is of boolean array type. + */ + public boolean isBooleanArray() { + return m_type == NetworkTableType.kBooleanArray; + } + + /** + * Determine if entry value contains a double array. + * @return True if the entry value is of double array type. + */ + public boolean isDoubleArray() { + return m_type == NetworkTableType.kDoubleArray; + } + + /** + * Determine if entry value contains a string array. + * @return True if the entry value is of string array type. + */ + public boolean isStringArray() { + return m_type == NetworkTableType.kStringArray; + } + + /* + * Type-Safe Getters + */ + + /** + * Get the entry's boolean value. + * @throws ClassCastException if the entry value is not of boolean type. + * @return The boolean value. + */ + public boolean getBoolean() { + if (m_type != NetworkTableType.kBoolean) { + throw new ClassCastException("cannot convert " + m_type + " to boolean"); + } + return ((Boolean)m_value).booleanValue(); + } + + /** + * Get the entry's double value. + * @throws ClassCastException if the entry value is not of double type. + * @return The double value. + */ + public double getDouble() { + if (m_type != NetworkTableType.kDouble) { + throw new ClassCastException("cannot convert " + m_type + " to double"); + } + return ((Number)m_value).doubleValue(); + } + + /** + * Get the entry's string value. + * @throws ClassCastException if the entry value is not of string type. + * @return The string value. + */ + public String getString() { + if (m_type != NetworkTableType.kString) { + throw new ClassCastException("cannot convert " + m_type + " to string"); + } + return (String)m_value; + } + + /** + * Get the entry's raw value. + * @throws ClassCastException if the entry value is not of raw type. + * @return The raw value. + */ + public byte[] getRaw() { + if (m_type != NetworkTableType.kRaw) { + throw new ClassCastException("cannot convert " + m_type + " to raw"); + } + return (byte[])m_value; + } + + /** + * Get the entry's rpc definition value. + * @throws ClassCastException if the entry value is not of rpc definition type. + * @return The rpc definition value. + */ + public byte[] getRpc() { + if (m_type != NetworkTableType.kRpc) { + throw new ClassCastException("cannot convert " + m_type + " to rpc"); + } + return (byte[])m_value; + } + + /** + * Get the entry's boolean array value. + * @throws ClassCastException if the entry value is not of boolean array type. + * @return The boolean array value. + */ + public boolean[] getBooleanArray() { + if (m_type != NetworkTableType.kBooleanArray) { + throw new ClassCastException("cannot convert " + m_type + " to boolean array"); + } + return (boolean[])m_value; + } + + /** + * Get the entry's double array value. + * @throws ClassCastException if the entry value is not of double array type. + * @return The double array value. + */ + public double[] getDoubleArray() { + if (m_type != NetworkTableType.kDoubleArray) { + throw new ClassCastException("cannot convert " + m_type + " to double array"); + } + return (double[])m_value; + } + + /** + * Get the entry's string array value. + * @throws ClassCastException if the entry value is not of string array type. + * @return The string array value. + */ + public String[] getStringArray() { + if (m_type != NetworkTableType.kStringArray) { + throw new ClassCastException("cannot convert " + m_type + " to string array"); + } + return (String[])m_value; + } + + /* + * Factory functions. + */ + + /** + * Creates a boolean entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeBoolean(boolean value) { + return new NetworkTableValue(NetworkTableType.kBoolean, new Boolean(value)); + } + + /** + * Creates a boolean entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeBoolean(boolean value, long time) { + return new NetworkTableValue(NetworkTableType.kBoolean, new Boolean(value), time); + } + + /** + * Creates a double entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeDouble(double value) { + return new NetworkTableValue(NetworkTableType.kDouble, new Double(value)); + } + + /** + * Creates a double entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeDouble(double value, long time) { + return new NetworkTableValue(NetworkTableType.kDouble, new Double(value), time); + } + + /** + * Creates a string entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeString(String value) { + return new NetworkTableValue(NetworkTableType.kString, value); + } + + /** + * Creates a string entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeString(String value, long time) { + return new NetworkTableValue(NetworkTableType.kString, value, time); + } + + /** + * Creates a raw entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeRaw(byte[] value) { + return new NetworkTableValue(NetworkTableType.kRaw, value); + } + + /** + * Creates a raw entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeRaw(byte[] value, long time) { + return new NetworkTableValue(NetworkTableType.kRaw, value, time); + } + + /** + * Creates a rpc entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeRpc(byte[] value) { + return new NetworkTableValue(NetworkTableType.kRpc, value); + } + + /** + * Creates a rpc entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeRpc(byte[] value, long time) { + return new NetworkTableValue(NetworkTableType.kRpc, value, time); + } + + /** + * Creates a boolean array entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeBooleanArray(boolean[] value) { + return new NetworkTableValue(NetworkTableType.kBooleanArray, value); + } + + /** + * Creates a boolean array entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeBooleanArray(boolean[] value, long time) { + return new NetworkTableValue(NetworkTableType.kBooleanArray, value, time); + } + + /** + * Creates a boolean array entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeBooleanArray(Boolean[] value) { + return new NetworkTableValue(NetworkTableType.kBooleanArray, toNative(value)); + } + + /** + * Creates a boolean array entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeBooleanArray(Boolean[] value, long time) { + return new NetworkTableValue(NetworkTableType.kBooleanArray, toNative(value), time); + } + + /** + * Creates a double array entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeDoubleArray(double[] value) { + return new NetworkTableValue(NetworkTableType.kDoubleArray, value); + } + + /** + * Creates a double array entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeDoubleArray(double[] value, long time) { + return new NetworkTableValue(NetworkTableType.kDoubleArray, value, time); + } + + /** + * Creates a double array entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeDoubleArray(Number[] value) { + return new NetworkTableValue(NetworkTableType.kDoubleArray, toNative(value)); + } + + /** + * Creates a double array entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeDoubleArray(Number[] value, long time) { + return new NetworkTableValue(NetworkTableType.kDoubleArray, toNative(value), time); + } + + /** + * Creates a string array entry value. + * @param value the value + * @return The entry value + */ + public static NetworkTableValue makeStringArray(String[] value) { + return new NetworkTableValue(NetworkTableType.kStringArray, value); + } + + /** + * Creates a string array entry value. + * @param value the value + * @param time the creation time to use (instead of the current time) + * @return The entry value + */ + public static NetworkTableValue makeStringArray(String[] value, long time) { + return new NetworkTableValue(NetworkTableType.kStringArray, value, time); + } + + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof NetworkTableValue)) { + return false; + } + NetworkTableValue other = (NetworkTableValue) o; + return m_type == other.m_type && m_value.equals(other.m_value); + } + + @Override + public int hashCode() { + return Objects.hash(m_type, m_value); + } + + static boolean[] toNative(Boolean[] arr) { + boolean[] out = new boolean[arr.length]; + for (int i = 0; i < arr.length; i++) + out[i] = arr[i]; + return out; + } + + static double[] toNative(Number[] arr) { + double[] out = new double[arr.length]; + for (int i = 0; i < arr.length; i++) + out[i] = arr[i].doubleValue(); + return out; + } + + static Boolean[] fromNative(boolean[] arr) { + Boolean[] out = new Boolean[arr.length]; + for (int i = 0; i < arr.length; i++) + out[i] = arr[i]; + return out; + } + + static Double[] fromNative(double[] arr) { + Double[] out = new Double[arr.length]; + for (int i = 0; i < arr.length; i++) + out[i] = arr[i]; + return out; + } + + private NetworkTableType m_type; + private Object m_value; + private long m_time; +} diff --git a/src/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java b/src/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java new file mode 100644 index 0000000..b8e2452 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/NetworkTablesJNI.java @@ -0,0 +1,182 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +import java.io.File; +import java.io.InputStream; +import java.io.OutputStream; +import java.io.FileOutputStream; +import java.io.IOException; +import java.nio.ByteBuffer; +import edu.wpi.first.wpiutil.RuntimeDetector; + +public final class NetworkTablesJNI { + static boolean libraryLoaded = false; + static File jniLibrary = null; + static { + if (!libraryLoaded) { + try { + System.loadLibrary("ntcore"); + } catch (UnsatisfiedLinkError e) { + try { + String resname = RuntimeDetector.getLibraryResource("ntcore"); + InputStream is = NetworkTablesJNI.class.getResourceAsStream(resname); + if (is != null) { + // create temporary file + if (System.getProperty("os.name").startsWith("Windows")) + jniLibrary = File.createTempFile("NetworkTablesJNI", ".dll"); + else if (System.getProperty("os.name").startsWith("Mac")) + jniLibrary = File.createTempFile("libNetworkTablesJNI", ".dylib"); + else + jniLibrary = File.createTempFile("libNetworkTablesJNI", ".so"); + // flag for delete on exit + jniLibrary.deleteOnExit(); + OutputStream os = new FileOutputStream(jniLibrary); + + byte[] buffer = new byte[1024]; + int readBytes; + try { + while ((readBytes = is.read(buffer)) != -1) { + os.write(buffer, 0, readBytes); + } + } finally { + os.close(); + is.close(); + } + System.load(jniLibrary.getAbsolutePath()); + } else { + System.loadLibrary("ntcore"); + } + } catch (IOException ex) { + ex.printStackTrace(); + System.exit(1); + } + } + libraryLoaded = true; + } + } + + public static native int getDefaultInstance(); + public static native int createInstance(); + public static native void destroyInstance(int inst); + public static native int getInstanceFromHandle(int handle); + + public static native int getEntry(int inst, String key); + public static native int[] getEntries(int inst, String prefix, int types); + public static native String getEntryName(int entry); + public static native long getEntryLastChange(int entry); + + public static native int getType(int entry); + + public static native boolean setBoolean(int entry, long time, boolean value, boolean force); + public static native boolean setDouble(int entry, long time, double value, boolean force); + public static native boolean setString(int entry, long time, String value, boolean force); + public static native boolean setRaw(int entry, long time, byte[] value, boolean force); + public static native boolean setRaw(int entry, long time, ByteBuffer value, int len, boolean force); + public static native boolean setBooleanArray(int entry, long time, boolean[] value, boolean force); + public static native boolean setDoubleArray(int entry, long time, double[] value, boolean force); + public static native boolean setStringArray(int entry, long time, String[] value, boolean force); + + public static native NetworkTableValue getValue(int entry); + + public static native boolean getBoolean(int entry, boolean defaultValue); + public static native double getDouble(int entry, double defaultValue); + public static native String getString(int entry, String defaultValue); + public static native byte[] getRaw(int entry, byte[] defaultValue); + public static native boolean[] getBooleanArray(int entry, boolean[] defaultValue); + public static native double[] getDoubleArray(int entry, double[] defaultValue); + public static native String[] getStringArray(int entry, String[] defaultValue); + public static native boolean setDefaultBoolean(int entry, long time, boolean defaultValue); + + public static native boolean setDefaultDouble(int entry, long time, double defaultValue); + public static native boolean setDefaultString(int entry, long time, String defaultValue); + public static native boolean setDefaultRaw(int entry, long time, byte[] defaultValue); + public static native boolean setDefaultBooleanArray(int entry, long time, boolean[] defaultValue); + public static native boolean setDefaultDoubleArray(int entry, long time, double[] defaultValue); + public static native boolean setDefaultStringArray(int entry, long time, String[] defaultValue); + + public static native void setEntryFlags(int entry, int flags); + public static native int getEntryFlags(int entry); + + public static native void deleteEntry(int entry); + + public static native void deleteAllEntries(int inst); + + public static native EntryInfo getEntryInfoHandle(NetworkTableInstance inst, int entry); + public static native EntryInfo[] getEntryInfo(NetworkTableInstance instObject, int inst, String prefix, int types); + + public static native int createEntryListenerPoller(int inst); + public static native void destroyEntryListenerPoller(int poller); + public static native int addPolledEntryListener(int poller, String prefix, int flags); + public static native int addPolledEntryListener(int poller, int entry, int flags); + public static native EntryNotification[] pollEntryListener(NetworkTableInstance inst, int poller) throws InterruptedException; + public static native EntryNotification[] pollEntryListenerTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException; + public static native void cancelPollEntryListener(int poller); + public static native void removeEntryListener(int entryListener); + public static native boolean waitForEntryListenerQueue(int inst, double timeout); + + public static native int createConnectionListenerPoller(int inst); + public static native void destroyConnectionListenerPoller(int poller); + public static native int addPolledConnectionListener(int poller, boolean immediateNotify); + public static native ConnectionNotification[] pollConnectionListener(NetworkTableInstance inst, int poller) throws InterruptedException; + public static native ConnectionNotification[] pollConnectionListenerTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException; + public static native void cancelPollConnectionListener(int poller); + public static native void removeConnectionListener(int connListener); + public static native boolean waitForConnectionListenerQueue(int inst, double timeout); + + public static native int createRpcCallPoller(int inst); + public static native void destroyRpcCallPoller(int poller); + public static native void createPolledRpc(int entry, byte[] def, int poller); + public static native RpcAnswer[] pollRpc(NetworkTableInstance inst, int poller) throws InterruptedException; + public static native RpcAnswer[] pollRpcTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException; + public static native void cancelPollRpc(int poller); + public static native boolean waitForRpcCallQueue(int inst, double timeout); + public static native void postRpcResponse(int entry, int call, byte[] result); + public static native int callRpc(int entry, byte[] params); + public static native byte[] getRpcResult(int entry, int call); + public static native byte[] getRpcResult(int entry, int call, double timeout); + public static native void cancelRpcResult(int entry, int call); + + public static native byte[] getRpc(int entry, byte[] defaultValue); + + public static native void setNetworkIdentity(int inst, String name); + public static native int getNetworkMode(int inst); + public static native void startServer(int inst, String persistFilename, String listenAddress, int port); + public static native void stopServer(int inst); + public static native void startClient(int inst); + public static native void startClient(int inst, String serverName, int port); + public static native void startClient(int inst, String[] serverNames, int[] ports); + public static native void startClientTeam(int inst, int team, int port); + public static native void stopClient(int inst); + public static native void setServer(int inst, String serverName, int port); + public static native void setServer(int inst, String[] serverNames, int[] ports); + public static native void setServerTeam(int inst, int team, int port); + public static native void startDSClient(int inst, int port); + public static native void stopDSClient(int inst); + public static native void setUpdateRate(int inst, double interval); + + public static native void flush(int inst); + + public static native ConnectionInfo[] getConnections(int inst); + + public static native boolean isConnected(int inst); + + public static native void savePersistent(int inst, String filename) throws PersistentException; + public static native String[] loadPersistent(int inst, String filename) throws PersistentException; // returns warnings + + public static native long now(); + + public static native int createLoggerPoller(int inst); + public static native void destroyLoggerPoller(int poller); + public static native int addPolledLogger(int poller, int minLevel, int maxLevel); + public static native LogMessage[] pollLogger(NetworkTableInstance inst, int poller) throws InterruptedException; + public static native LogMessage[] pollLoggerTimeout(NetworkTableInstance inst, int poller, double timeout) throws InterruptedException; + public static native void cancelPollLogger(int poller); + public static native void removeLogger(int logger); + public static native boolean waitForLoggerQueue(int inst, double timeout); +} diff --git a/src/main/java/edu/wpi/first/networktables/PersistentException.java b/src/main/java/edu/wpi/first/networktables/PersistentException.java new file mode 100644 index 0000000..338d234 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/PersistentException.java @@ -0,0 +1,27 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +import java.io.IOException; + +/** + * An exception thrown when persistent load/save fails in a {@link NetworkTable} + * + */ +public final class PersistentException extends IOException { + + public static final long serialVersionUID = 0; + + /** + * @param message The error message + */ + public PersistentException(String message) { + super(message); + } + +} diff --git a/src/main/java/edu/wpi/first/networktables/RpcAnswer.java b/src/main/java/edu/wpi/first/networktables/RpcAnswer.java new file mode 100644 index 0000000..ad908ba --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/RpcAnswer.java @@ -0,0 +1,91 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * NetworkTables Remote Procedure Call (Server Side). + */ +public final class RpcAnswer { + /** Entry handle. */ + public final int entry; + + /** Call handle. */ + public int call; + + /** Entry name. */ + public final String name; + + /** Call raw parameters. */ + public final String params; + + /** Connection that called the RPC. */ + public final ConnectionInfo conn; + + /** Constructor. + * This should generally only be used internally to NetworkTables. + * @param inst Instance + * @param entry Entry handle + * @param call Call handle + * @param name Entry name + * @param params Call raw parameters + * @param conn Connection info + */ + public RpcAnswer(NetworkTableInstance inst, int entry, int call, String name, String params, ConnectionInfo conn) { + this.inst = inst; + this.entry = entry; + this.call = call; + this.name = name; + this.params = params; + this.conn = conn; + } + + static final byte[] emptyResponse = new byte[] {}; + + /** + * Posts an empty response if one was not previously sent. + */ + public synchronized void free() { + if (call != 0) { + postResponse(emptyResponse); + } + } + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + public boolean isValid() { + return call != 0; + } + + /** + * Post RPC response (return value) for a polled RPC. + * @param result result raw data that will be provided to remote caller + */ + public void postResponse(byte[] result) { + NetworkTablesJNI.postRpcResponse(entry, call, result); + call = 0; + } + + /* Network table instance. */ + private final NetworkTableInstance inst; + + /* Cached entry object. */ + NetworkTableEntry entryObject; + + /** + * Get the entry as an object. + * @return NetworkTableEntry for the RPC. + */ + NetworkTableEntry getEntry() { + if (entryObject == null) { + entryObject = new NetworkTableEntry(inst, entry); + } + return entryObject; + } +} diff --git a/src/main/java/edu/wpi/first/networktables/RpcCall.java b/src/main/java/edu/wpi/first/networktables/RpcCall.java new file mode 100644 index 0000000..4ddff3f --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/RpcCall.java @@ -0,0 +1,93 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * NetworkTables Remote Procedure Call. + */ +public final class RpcCall { + /** Constructor. + * This should generally only be used internally to NetworkTables. + * @param entry Entry + * @param call Call handle + */ + public RpcCall(NetworkTableEntry entry, int call) { + m_entry = entry; + m_call = call; + } + + /** + * Cancels the result if no other action taken. + */ + public synchronized void free() { + if (m_call != 0) { + cancelResult(); + } + } + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + public boolean isValid() { + return m_call != 0; + } + + /** + * Get the RPC entry. + * @return NetworkTableEntry for the RPC. + */ + public NetworkTableEntry getEntry() { + return m_entry; + } + + /** + * Get the call native handle. + * @return Native handle. + */ + public int getCall() { + return m_call; + } + + /** + * Get the result (return value). This function blocks until + * the result is received. + * @return Received result (output) + */ + public byte[] getResult() { + byte[] result = NetworkTablesJNI.getRpcResult(m_entry.getHandle(), m_call); + if (result.length != 0) { + m_call = 0; + } + return result; + } + + /** + * Get the result (return value). This function blocks until + * the result is received or it times out. + * @param timeout timeout, in seconds + * @return Received result (output) + */ + public byte[] getResult(double timeout) { + byte[] result = NetworkTablesJNI.getRpcResult(m_entry.getHandle(), m_call, timeout); + if (result.length != 0) { + m_call = 0; + } + return result; + } + + /** + * Ignore the result. This function is non-blocking. + */ + public void cancelResult() { + NetworkTablesJNI.cancelRpcResult(m_entry.getHandle(), m_call); + } + + private final NetworkTableEntry m_entry; + private int m_call; +} diff --git a/src/main/java/edu/wpi/first/networktables/TableEntryListener.java b/src/main/java/edu/wpi/first/networktables/TableEntryListener.java new file mode 100644 index 0000000..c376072 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/TableEntryListener.java @@ -0,0 +1,26 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * A listener that listens to changes in values in a {@link NetworkTable} + */ +@FunctionalInterface +public interface TableEntryListener extends EntryListenerFlags { + /** + * Called when a key-value pair is changed in a {@link NetworkTable}. + * + * @param table the table the key-value pair exists in + * @param key the key associated with the value that changed + * @param entry the entry associated with the value that changed + * @param value the new value + * @param flags update flags; for example, EntryListenerFlags.kNew if the key + * did not previously exist in the table + */ + void valueChanged(NetworkTable table, String key, NetworkTableEntry entry, NetworkTableValue value, int flags); +} diff --git a/src/main/java/edu/wpi/first/networktables/TableListener.java b/src/main/java/edu/wpi/first/networktables/TableListener.java new file mode 100644 index 0000000..6725b76 --- /dev/null +++ b/src/main/java/edu/wpi/first/networktables/TableListener.java @@ -0,0 +1,23 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +package edu.wpi.first.networktables; + +/** + * A listener that listens to new tables in a {@link NetworkTable} + */ +@FunctionalInterface +public interface TableListener { + /** + * Called when a new table is created within a {@link NetworkTable}. + * + * @param parent the parent of the table + * @param name the name of the new table + * @param table the new table + */ + void tableCreated(NetworkTable parent, String name, NetworkTable table); +} diff --git a/src/main/java/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java b/src/main/java/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java deleted file mode 100644 index 03ae474..0000000 --- a/src/main/java/edu/wpi/first/wpilibj/networktables/ConnectionInfo.java +++ /dev/null @@ -1,17 +0,0 @@ -package edu.wpi.first.wpilibj.networktables; - -public class ConnectionInfo { - public final String remote_id; - public final String remote_ip; - public final int remote_port; - public final long last_update; - public final int protocol_version; - - public ConnectionInfo(String remote_id, String remote_ip, int remote_port, long last_update, int protocol_version) { - this.remote_id = remote_id; - this.remote_ip = remote_ip; - this.remote_port = remote_port; - this.last_update = last_update; - this.protocol_version = protocol_version; - } -} diff --git a/src/main/java/edu/wpi/first/wpilibj/networktables/EntryInfo.java b/src/main/java/edu/wpi/first/wpilibj/networktables/EntryInfo.java deleted file mode 100644 index 15c1980..0000000 --- a/src/main/java/edu/wpi/first/wpilibj/networktables/EntryInfo.java +++ /dev/null @@ -1,15 +0,0 @@ -package edu.wpi.first.wpilibj.networktables; - -public class EntryInfo { - public final String name; - public final int type; - public final int flags; - public final long last_change; - - public EntryInfo(String name, int type, int flags, long last_change) { - this.name = name; - this.type = type; - this.flags = flags; - this.last_change = last_change; - } -} diff --git a/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTable.java b/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTable.java index 12de1cb..944bad3 100644 --- a/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTable.java +++ b/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTable.java @@ -1,13 +1,42 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + package edu.wpi.first.wpilibj.networktables; -import edu.wpi.first.wpilibj.tables.*; -import java.io.*; +import edu.wpi.first.networktables.ConnectionInfo; +import edu.wpi.first.networktables.ConnectionNotification; +import edu.wpi.first.networktables.EntryInfo; +import edu.wpi.first.networktables.EntryNotification; +import edu.wpi.first.networktables.NetworkTableEntry; +import edu.wpi.first.networktables.NetworkTableInstance; +import edu.wpi.first.networktables.NetworkTableType; +import edu.wpi.first.networktables.NetworkTableValue; +import edu.wpi.first.networktables.NetworkTablesJNI; +import edu.wpi.first.networktables.PersistentException; +import edu.wpi.first.wpilibj.tables.IRemote; +import edu.wpi.first.wpilibj.tables.IRemoteConnectionListener; +import edu.wpi.first.wpilibj.tables.ITable; +import edu.wpi.first.wpilibj.tables.ITableListener; import java.nio.ByteBuffer; -import java.util.*; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.HashSet; +import java.util.List; +import java.util.Objects; +import java.util.Set; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.ConcurrentMap; +import java.util.function.Consumer; /** * A network table that knows its subtable path. + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable} instead. */ +@Deprecated public class NetworkTable implements ITable, IRemote { /** * The path separator for sub-tables and keys @@ -33,37 +62,47 @@ private synchronized static void checkInit() { /** * initializes network tables + * @deprecated Use {@link NetworkTableInstance#startServer()} or + * {@link NetworkTableInstance#startClient()} instead. */ + @Deprecated public synchronized static void initialize() { if (running) shutdown(); + NetworkTableInstance inst = NetworkTableInstance.getDefault(); if (client) { - NetworkTablesJNI.startClient(); + inst.startClient(); if (enableDS) - NetworkTablesJNI.startDSClient(port); + inst.startDSClient(port); } else - NetworkTablesJNI.startServer(persistentFilename, "", port); + inst.startServer(persistentFilename, "", port); running = true; } /** * shuts down network tables + * @deprecated Use {@link NetworkTableInstance#stopServer()} or + * {@link NetworkTableInstance#stopClient()} instead. */ + @Deprecated public synchronized static void shutdown() { if (!running) return; + NetworkTableInstance inst = NetworkTableInstance.getDefault(); if (client) { - NetworkTablesJNI.stopDSClient(); - NetworkTablesJNI.stopClient(); + inst.stopDSClient(); + inst.stopClient(); } else - NetworkTablesJNI.stopServer(); + inst.stopServer(); running = false; } /** * set that network tables should be a server * This must be called before initialize or getTable + * @deprecated Use {@link NetworkTableInstance#startServer()} instead. */ + @Deprecated public synchronized static void setServerMode() { if (!client) return; @@ -74,7 +113,9 @@ public synchronized static void setServerMode() { /** * set that network tables should be a client * This must be called before initialize or getTable + * @deprecated Use {@link NetworkTableInstance#startClient()} instead. */ + @Deprecated public synchronized static void setClientMode() { if (client) return; @@ -87,21 +128,24 @@ public synchronized static void setClientMode() { * network tables will connect to in client mode) * This must be called before initialize or getTable * @param team the team number + * @deprecated Use {@link NetworkTableInstance#setServerTeam(int)} or + * {@link NetworkTableInstance#startClientTeam(int)} instead. */ + @Deprecated public synchronized static void setTeam(int team) { - String[] addresses = new String[5]; - addresses[0] = "10." + (int)(team / 100) + "." + (int)(team % 100) + ".2"; - addresses[1] = "172.22.11.2"; - addresses[2] = "roboRIO-" + team + "-FRC.local"; - addresses[3] = "roboRIO-" + team + "-FRC.lan"; - addresses[4] = "roboRIO-" + team + "-FRC.frc-field.local"; - setIPAddress(addresses); + NetworkTableInstance inst = NetworkTableInstance.getDefault(); + inst.setServerTeam(team, port); + if (enableDS) + inst.startDSClient(port); } /** - * @param address the adress that network tables will connect to in client + * @param address the address that network tables will connect to in client * mode + * @deprecated Use {@link NetworkTableInstance#setServer(String)} or + * {@link NetworkTableInstance#startClient(String)} instead. */ + @Deprecated public synchronized static void setIPAddress(final String address) { String[] addresses = new String[1]; addresses[0] = address; @@ -111,25 +155,33 @@ public synchronized static void setIPAddress(final String address) { /** * @param addresses the adresses that network tables will connect to in * client mode (in round robin order) + * @deprecated Use {@link NetworkTableInstance#setServer(String[])} or + * {@link NetworkTableInstance#startClient(String[])} instead. */ + @Deprecated public synchronized static void setIPAddress(final String[] addresses) { - int[] ports = new int[addresses.length]; - for (int i=0; i 0 && (addresses[0].equals("localhost") || addresses[0].equals("127.0.0.1"))) - NetworkTablesJNI.stopDSClient(); + inst.stopDSClient(); else if (enableDS) - NetworkTablesJNI.startDSClient(port); + inst.startDSClient(port); } /** - * @param aport the port number that network tables will connect to in client - * mode or listen to in server mode + * Set the port number that network tables will connect to in client + * mode or listen to in server mode. + * @param aport the port number + * @deprecated Use the appropriate parameters to + * {@link NetworkTableInstance#setServer(String, int)}, + * {@link NetworkTableInstance#startClient(String, int)}, + * {@link NetworkTableInstance#startServer(String, String, int)}, and + * {@link NetworkTableInstance#startDSClient(int)} instead. */ + @Deprecated public synchronized static void setPort(int aport) { if (port == aport) return; @@ -138,22 +190,29 @@ public synchronized static void setPort(int aport) { } /** - * @param enabled whether to enable the connection to the local DS to get - * the robot IP address (defaults to enabled) + * Enable requesting the server address from the Driver Station. + * @param enabled whether to enable the connection to the local DS + * @deprecated Use {@link NetworkTableInstance#startDSClient()} and + * {@link NetworkTableInstance#stopDSClient()} instead. */ + @Deprecated public synchronized static void setDSClientEnabled(boolean enabled) { + NetworkTableInstance inst = NetworkTableInstance.getDefault(); enableDS = enabled; if (enableDS) - NetworkTablesJNI.startDSClient(port); + inst.startDSClient(port); else - NetworkTablesJNI.stopDSClient(); + inst.stopDSClient(); } /** * Sets the persistent filename. * @param filename the filename that the network tables server uses for * automatic loading and saving of persistent values + * @deprecated Use the appropriate parameter to + * {@link NetworkTableInstance#startServer()} instead. */ + @Deprecated public synchronized static void setPersistentFilename(final String filename) { if (persistentFilename.equals(filename)) return; @@ -165,9 +224,12 @@ public synchronized static void setPersistentFilename(final String filename) { * Sets the network identity. * This is provided in the connection info on the remote end. * @param name identity + * @deprecated Use {@link NetworkTableInstance#setNetworkIdentity(String)} + * instead. */ + @Deprecated public static void setNetworkIdentity(String name) { - NetworkTablesJNI.setNetworkIdentity(name); + NetworkTableInstance.getDefault().setNetworkIdentity(name); } public static boolean[] toNative(Boolean[] arr) { @@ -200,49 +262,91 @@ public static Double[] fromNative(double[] arr) { /** * Gets the table with the specified key. If the table does not exist, a new - *table will be created.
+ * table will be created.
* This will automatically initialize network tables if it has not been - *already + * already * - * @param key - * the key name + * @deprecated Use {@link NetworkTableInstance#getTable(String)} instead. + * + * @param key the key name * @return the network table requested */ + @Deprecated public synchronized static NetworkTable getTable(String key) { if (!running) initialize(); - if (key.isEmpty() || key.charAt(0) == PATH_SEPARATOR) - return new NetworkTable(key); - return new NetworkTable(PATH_SEPARATOR + key); + String theKey; + if (key.isEmpty() || key.equals("/")) { + theKey = ""; + } else if (key.charAt(0) == NetworkTable.PATH_SEPARATOR) { + theKey = key; + } else { + theKey = NetworkTable.PATH_SEPARATOR + key; + } + return new NetworkTable(NetworkTableInstance.getDefault(), theKey); } private final String path; private final String pathWithSep; + private final NetworkTableInstance inst; - NetworkTable(String path) { + NetworkTable(NetworkTableInstance inst, String path) { this.path = path; this.pathWithSep = path + PATH_SEPARATOR; + this.inst = inst; } + public String toString() { return "NetworkTable: " + path; } + private final ConcurrentMap entries = new ConcurrentHashMap(); + + /** + * Gets the entry for a subkey. + * @param key the key name + * @return Network table entry. + */ + private NetworkTableEntry getEntry(String key) { + NetworkTableEntry entry = entries.get(key); + if (entry == null) { + entry = inst.getEntry(pathWithSep + key); + entries.putIfAbsent(key, entry); + } + return entry; + } + + /** + * Gets the current network connections. + * @return An array of connection information. + * @deprecated Use {@link NetworkTableInstance#getConnections()} instead. + */ + @Deprecated public static ConnectionInfo[] connections() { - return NetworkTablesJNI.getConnections(); + return NetworkTableInstance.getDefault().getConnections(); } + /** + * Determine whether or not a network connection is active. + * @return True if connected, false if not connected. + * @deprecated Use {@link NetworkTableInstance#isConnected()} instead. + */ + @Deprecated public boolean isConnected() { - ConnectionInfo[] conns = NetworkTablesJNI.getConnections(); - return conns.length > 0; + return inst.isConnected(); } + /** + * Determine whether NetworkTables is operating as a server or as a client. + * @return True if operating as a server, false otherwise. + * @deprecated Use {@link NetworkTableInstance#getNetworkMode()} instead. + */ + @Deprecated public boolean isServer() { - return !client; + return (inst.getNetworkMode() & NetworkTableInstance.kNetModeServer) != 0; } - private static class ListenerBase { + /* Backwards compatibility shims for IRemoteConnectionListener */ + private static class ConnectionListenerAdapter implements Consumer { public int uid; - } - - private static class ConnectionListenerAdapter extends ListenerBase implements NetworkTablesJNI.ConnectionListenerFunction { private final IRemote targetSource; private final IRemoteConnectionListener targetListener; @@ -251,14 +355,17 @@ public ConnectionListenerAdapter(IRemote targetSource, IRemoteConnectionListener this.targetListener = targetListener; } - public void apply(int uid, boolean connected, ConnectionInfo conn) { - if (connected) - targetListener.connectedEx(targetSource, conn); + @Override + public void accept(ConnectionNotification event) { + if (event.connected) + targetListener.connectedEx(targetSource, event.conn); else - targetListener.disconnectedEx(targetSource, conn); + targetListener.disconnectedEx(targetSource, event.conn); } } - + + private static final HashMap globalConnectionListenerMap = new HashMap(); + private static IRemote staticRemote = new IRemote() { public void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) { NetworkTable.addGlobalConnectionListener(listener, immediateNotify); @@ -267,64 +374,91 @@ public void removeConnectionListener(IRemoteConnectionListener listener) { NetworkTable.removeGlobalConnectionListener(listener); } public boolean isConnected() { - ConnectionInfo[] conns = NetworkTablesJNI.getConnections(); + ConnectionInfo[] conns = NetworkTableInstance.getDefault().getConnections(); return conns.length > 0; } public boolean isServer() { - return !client; + return (NetworkTableInstance.getDefault().getNetworkMode() & NetworkTableInstance.kNetModeServer) != 0; } }; - - private static final Hashtable globalConnectionListenerMap = new Hashtable(); - public static synchronized void addGlobalConnectionListener(IRemoteConnectionListener listener, - boolean immediateNotify) { - ConnectionListenerAdapter adapter = globalConnectionListenerMap.get(listener); - if (adapter != null) + + private final HashMap connectionListenerMap = new HashMap(); + + /** + * Add a connection listener. + * @param listener connection listener + * @param immediateNotify call listener immediately for all existing connections + * @deprecated Use {@link NetworkTableInstance#addConnectionListener(Consumer, boolean)} instead. + */ + @Deprecated + public static synchronized void addGlobalConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) { + ConnectionListenerAdapter adapter = new ConnectionListenerAdapter(staticRemote, listener); + if (globalConnectionListenerMap.putIfAbsent(listener, adapter) != null) { throw new IllegalStateException("Cannot add the same listener twice"); - adapter = new ConnectionListenerAdapter(staticRemote, listener); - adapter.uid = NetworkTablesJNI.addConnectionListener(adapter, immediateNotify); - globalConnectionListenerMap.put(listener, adapter); + } + adapter.uid = NetworkTableInstance.getDefault().addConnectionListener(adapter, immediateNotify); } + /** + * Remove a connection listener. + * @param listener connection listener + * @deprecated Use {@link NetworkTableInstance#removeConnectionListener(int)} instead. + */ + @Deprecated public static synchronized void removeGlobalConnectionListener(IRemoteConnectionListener listener) { - ConnectionListenerAdapter adapter = globalConnectionListenerMap.get(listener); + ConnectionListenerAdapter adapter = globalConnectionListenerMap.remove(listener); if (adapter != null) { - NetworkTablesJNI.removeConnectionListener(adapter.uid); - globalConnectionListenerMap.remove(listener); + NetworkTableInstance.getDefault().removeConnectionListener(adapter.uid); } } - private final Hashtable connectionListenerMap = new Hashtable(); + /** + * Add a connection listener. + * @param listener connection listener + * @param immediateNotify call listener immediately for all existing connections + * @deprecated Use {@link NetworkTableInstance#addConnectionListener(Consumer, boolean)} instead. + */ + @Deprecated public synchronized void addConnectionListener(IRemoteConnectionListener listener, boolean immediateNotify) { - ConnectionListenerAdapter adapter = connectionListenerMap.get(listener); - if (adapter != null) + ConnectionListenerAdapter adapter = new ConnectionListenerAdapter(this, listener); + if (connectionListenerMap.putIfAbsent(listener, adapter) != null) { throw new IllegalStateException("Cannot add the same listener twice"); - adapter = new ConnectionListenerAdapter(this, listener); - adapter.uid = NetworkTablesJNI.addConnectionListener(adapter, immediateNotify); - connectionListenerMap.put(listener, adapter); + } + adapter.uid = inst.addConnectionListener(adapter, immediateNotify); } + /** + * Remove a connection listener. + * @param listener connection listener + * @deprecated Use {@link NetworkTableInstance#removeConnectionListener(int)} instead. + */ + @Deprecated public synchronized void removeConnectionListener(IRemoteConnectionListener listener) { ConnectionListenerAdapter adapter = connectionListenerMap.get(listener); - if (adapter != null) { - NetworkTablesJNI.removeConnectionListener(adapter.uid); - connectionListenerMap.remove(listener); + if (adapter != null && connectionListenerMap.remove(listener, adapter)) { + inst.removeConnectionListener(adapter.uid); } } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(TableEntryListener, int)} instead + * (with flags value of NOTIFY_NEW | NOTIFY_UPDATE). */ @Override + @Deprecated public void addTableListener(ITableListener listener) { addTableListenerEx(listener, NOTIFY_NEW | NOTIFY_UPDATE); } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(TableEntryListener, int)} instead + * (with flags value of NOTIFY_NEW | NOTIFY_UPDATE | NOTIFY_IMMEDIATE). */ @Override + @Deprecated public void addTableListener(ITableListener listener, boolean immediateNotify) { int flags = NOTIFY_NEW | NOTIFY_UPDATE; @@ -333,43 +467,59 @@ public void addTableListener(ITableListener listener, addTableListenerEx(listener, flags); } - private class TableListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction { + /* Base class for listeners; stores uid to implement remove functions */ + private static class ListenerBase { + public int uid; + } + + private class OldTableListenerAdapter extends ListenerBase implements Consumer { private final int prefixLen; private final ITable targetSource; private final ITableListener targetListener; - public TableListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) { + public OldTableListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) { this.prefixLen = prefixLen; this.targetSource = targetSource; this.targetListener = targetListener; } - public void apply(int uid, String key, Object value, int flags) { - String relativeKey = key.substring(prefixLen); + @Override + public void accept(EntryNotification event) { + String relativeKey = event.name.substring(prefixLen); if (relativeKey.indexOf(PATH_SEPARATOR) != -1) return; - targetListener.valueChangedEx(targetSource, relativeKey, value, flags); + targetListener.valueChangedEx(targetSource, relativeKey, event.value.getValue(), event.flags); } } - private final Hashtable> listenerMap = new Hashtable>(); + private final HashMap> oldListenerMap = new HashMap>(); + + /** + * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(TableEntryListener, int)} instead. + */ + @Override + @Deprecated public synchronized void addTableListenerEx(ITableListener listener, int flags) { - List adapters = listenerMap.get(listener); + List adapters = oldListenerMap.get(listener); if (adapters == null) { adapters = new ArrayList(); - listenerMap.put(listener, adapters); + oldListenerMap.put(listener, adapters); } - TableListenerAdapter adapter = - new TableListenerAdapter(path.length() + 1, this, listener); - adapter.uid = NetworkTablesJNI.addEntryListener(pathWithSep, adapter, flags); + OldTableListenerAdapter adapter = + new OldTableListenerAdapter(path.length() + 1, this, listener); + adapter.uid = inst.addEntryListener(pathWithSep, adapter, flags); adapters.add(adapter); } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(String, TableEntryListener, int)} + * or {@link NetworkTableEntry#addListener(Consumer, int)} instead. */ @Override + @Deprecated public void addTableListener(String key, ITableListener listener, boolean immediateNotify) { int flags = NOTIFY_NEW | NOTIFY_UPDATE; @@ -378,67 +528,69 @@ public void addTableListener(String key, ITableListener listener, addTableListenerEx(key, listener, flags); } - private class KeyListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction { + private class OldKeyListenerAdapter extends ListenerBase implements Consumer { private final String relativeKey; - private final String fullKey; private final ITable targetSource; private final ITableListener targetListener; - public KeyListenerAdapter(String relativeKey, String fullKey, ITable targetSource, ITableListener targetListener) { + public OldKeyListenerAdapter(String relativeKey, ITable targetSource, ITableListener targetListener) { this.relativeKey = relativeKey; - this.fullKey = fullKey; this.targetSource = targetSource; this.targetListener = targetListener; } - public void apply(int uid, String key, Object value, int flags) { - if (!key.equals(fullKey)) - return; - targetListener.valueChangedEx(targetSource, relativeKey, value, flags); + @Override + public void accept(EntryNotification event) { + targetListener.valueChangedEx(targetSource, relativeKey, event.value.getValue(), event.flags); } } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addEntryListener(String, TableEntryListener, int)} + * or {@link NetworkTableEntry#addListener(Consumer, int)} instead. */ @Override + @Deprecated public synchronized void addTableListenerEx(String key, ITableListener listener, int flags) { - List adapters = listenerMap.get(listener); + List adapters = oldListenerMap.get(listener); if (adapters == null) { adapters = new ArrayList(); - listenerMap.put(listener, adapters); + oldListenerMap.put(listener, adapters); } - String fullKey = pathWithSep + key; - KeyListenerAdapter adapter = - new KeyListenerAdapter(key, fullKey, this, listener); - adapter.uid = NetworkTablesJNI.addEntryListener(fullKey, adapter, flags); + OldKeyListenerAdapter adapter = new OldKeyListenerAdapter(key, this, listener); + adapter.uid = inst.addEntryListener(getEntry(key), adapter, flags); adapters.add(adapter); } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addSubTableListener(TableListener, boolean)} + * instead. */ @Override + @Deprecated public void addSubTableListener(final ITableListener listener) { addSubTableListener(listener, false); } - private class SubListenerAdapter extends ListenerBase implements NetworkTablesJNI.EntryListenerFunction { + private class OldSubListenerAdapter extends ListenerBase implements Consumer { private final int prefixLen; private final ITable targetSource; private final ITableListener targetListener; private final Set notifiedTables = new HashSet(); - public SubListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) { + public OldSubListenerAdapter(int prefixLen, ITable targetSource, ITableListener targetListener) { this.prefixLen = prefixLen; this.targetSource = targetSource; this.targetListener = targetListener; } - public void apply(int uid, String key, Object value, int flags) { - String relativeKey = key.substring(prefixLen); + @Override + public void accept(EntryNotification event) { + String relativeKey = event.name.substring(prefixLen); int endSubTable = relativeKey.indexOf(PATH_SEPARATOR); if (endSubTable == -1) return; @@ -446,40 +598,44 @@ public void apply(int uid, String key, Object value, int flags) { if (notifiedTables.contains(subTableKey)) return; notifiedTables.add(subTableKey); - targetListener.valueChangedEx(targetSource, subTableKey, targetSource.getSubTable(subTableKey), flags); + targetListener.valueChangedEx(targetSource, subTableKey, targetSource.getSubTable(subTableKey), event.flags); } } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#addSubTableListener(TableListener, boolean)} + * instead. */ @Override + @Deprecated public synchronized void addSubTableListener(final ITableListener listener, boolean localNotify) { - List adapters = listenerMap.get(listener); + List adapters = oldListenerMap.get(listener); if (adapters == null) { adapters = new ArrayList(); - listenerMap.put(listener, adapters); + oldListenerMap.put(listener, adapters); } - SubListenerAdapter adapter = - new SubListenerAdapter(path.length() + 1, this, listener); + OldSubListenerAdapter adapter = + new OldSubListenerAdapter(path.length() + 1, this, listener); int flags = NOTIFY_NEW | NOTIFY_IMMEDIATE; if (localNotify) flags |= NOTIFY_LOCAL; - adapter.uid = NetworkTablesJNI.addEntryListener(pathWithSep, adapter, flags); + adapter.uid = inst.addEntryListener(pathWithSep, adapter, flags); adapters.add(adapter); } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable#removeTableListener(int)} instead. */ @Override + @Deprecated public synchronized void removeTableListener(ITableListener listener) { - List adapters = listenerMap.get(listener); + List adapters = oldListenerMap.remove(listener); if (adapters != null) { - for (int i = 0; i < adapters.size(); ++i) - NetworkTablesJNI.removeEntryListener(adapters.get(i).uid); - adapters.clear(); + for (ListenerBase adapter : adapters) + inst.removeEntryListener(adapter.uid); } } @@ -488,7 +644,7 @@ public synchronized void removeTableListener(ITableListener listener) { */ @Override public ITable getSubTable(String key) { - return new NetworkTable(pathWithSep + key); + return new NetworkTable(inst, pathWithSep + key); } /** @@ -496,12 +652,12 @@ public ITable getSubTable(String key) { */ @Override public boolean containsKey(String key) { - return NetworkTablesJNI.containsKey(pathWithSep + key); + return getEntry(key).exists(); } public boolean containsSubTable(String key) { - EntryInfo[] entries = NetworkTablesJNI.getEntries(pathWithSep + key + PATH_SEPARATOR, 0); - return entries.length != 0; + int[] handles = NetworkTablesJNI.getEntries(inst.getHandle(), pathWithSep + key + PATH_SEPARATOR, 0); + return handles.length != 0; } /** @@ -511,11 +667,15 @@ public boolean containsSubTable(String key) { public Set getKeys(int types) { Set keys = new HashSet(); int prefixLen = path.length() + 1; - for (EntryInfo entry : NetworkTablesJNI.getEntries(pathWithSep, types)) { - String relativeKey = entry.name.substring(prefixLen); + for (EntryInfo info : inst.getEntryInfo(pathWithSep, types)) { + String relativeKey = info.name.substring(prefixLen); if (relativeKey.indexOf(PATH_SEPARATOR) != -1) continue; keys.add(relativeKey); + // populate entries as we go + if (entries.get(relativeKey) == null) { + entries.putIfAbsent(relativeKey, new NetworkTableEntry(inst, info.entry)); + } } return keys; } @@ -535,8 +695,8 @@ public Set getKeys() { public Set getSubTables() { Set keys = new HashSet(); int prefixLen = path.length() + 1; - for (EntryInfo entry : NetworkTablesJNI.getEntries(pathWithSep, 0)) { - String relativeKey = entry.name.substring(prefixLen); + for (EntryInfo info : inst.getEntryInfo(pathWithSep, 0)) { + String relativeKey = info.name.substring(prefixLen); int endSubTable = relativeKey.indexOf(PATH_SEPARATOR); if (endSubTable == -1) continue; @@ -550,15 +710,14 @@ public Set getSubTables() { */ @Override public boolean putNumber(String key, double value) { - return NetworkTablesJNI.putDouble(pathWithSep + key, value); + return getEntry(key).setNumber(value); } - + /** * {@inheritDoc} */ public boolean setDefaultNumber(String key, double defaultValue) { - return NetworkTablesJNI.setDefaultDouble(pathWithSep + key, - defaultValue); + return getEntry(key).setDefaultDouble(defaultValue); } /** @@ -566,7 +725,7 @@ public boolean setDefaultNumber(String key, double defaultValue) { */ @Override public double getNumber(String key, double defaultValue) { - return NetworkTablesJNI.getDouble(pathWithSep + key, defaultValue); + return getEntry(key).getDouble(defaultValue); } /** @@ -574,15 +733,14 @@ public double getNumber(String key, double defaultValue) { */ @Override public boolean putString(String key, String value) { - return NetworkTablesJNI.putString(pathWithSep + key, value); + return getEntry(key).setString(value); } - + /** * {@inheritDoc} */ public boolean setDefaultString(String key, String defaultValue) { - return NetworkTablesJNI.setDefaultString(pathWithSep + key, - defaultValue); + return getEntry(key).setDefaultString(defaultValue); } /** @@ -590,7 +748,7 @@ public boolean setDefaultString(String key, String defaultValue) { */ @Override public String getString(String key, String defaultValue) { - return NetworkTablesJNI.getString(pathWithSep + key, defaultValue); + return getEntry(key).getString(defaultValue); } /** @@ -598,15 +756,14 @@ public String getString(String key, String defaultValue) { */ @Override public boolean putBoolean(String key, boolean value) { - return NetworkTablesJNI.putBoolean(pathWithSep + key, value); + return getEntry(key).setBoolean(value); } - + /** * {@inheritDoc} */ public boolean setDefaultBoolean(String key, boolean defaultValue) { - return NetworkTablesJNI.setDefaultBoolean(pathWithSep + key, - defaultValue); + return getEntry(key).setDefaultBoolean(defaultValue); } /** @@ -614,7 +771,7 @@ public boolean setDefaultBoolean(String key, boolean defaultValue) { */ @Override public boolean getBoolean(String key, boolean defaultValue) { - return NetworkTablesJNI.getBoolean(pathWithSep + key, defaultValue); + return getEntry(key).getBoolean(defaultValue); } /** @@ -622,7 +779,7 @@ public boolean getBoolean(String key, boolean defaultValue) { */ @Override public boolean putBooleanArray(String key, boolean[] value) { - return NetworkTablesJNI.putBooleanArray(pathWithSep + key, value); + return getEntry(key).setBooleanArray(value); } /** @@ -630,23 +787,21 @@ public boolean putBooleanArray(String key, boolean[] value) { */ @Override public boolean putBooleanArray(String key, Boolean[] value) { - return putBooleanArray(key, toNative(value)); + return getEntry(key).setBooleanArray(value); } - + /** * {@inheritDoc} */ public boolean setDefaultBooleanArray(String key, boolean[] defaultValue) { - return NetworkTablesJNI.setDefaultBooleanArray(pathWithSep + key, - defaultValue); + return getEntry(key).setDefaultBooleanArray(defaultValue); } - + /** * {@inheritDoc} */ public boolean setDefaultBooleanArray(String key, Boolean[] defaultValue) { - return NetworkTablesJNI.setDefaultBooleanArray(pathWithSep + key, - toNative(defaultValue)); + return getEntry(key).setDefaultBooleanArray(defaultValue); } /** @@ -654,7 +809,7 @@ public boolean setDefaultBooleanArray(String key, Boolean[] defaultValue) { */ @Override public boolean[] getBooleanArray(String key, boolean[] defaultValue) { - return NetworkTablesJNI.getBooleanArray(pathWithSep + key, defaultValue); + return getEntry(key).getBooleanArray(defaultValue); } /** @@ -662,7 +817,7 @@ public boolean[] getBooleanArray(String key, boolean[] defaultValue) { */ @Override public Boolean[] getBooleanArray(String key, Boolean[] defaultValue) { - return fromNative(getBooleanArray(key, toNative(defaultValue))); + return getEntry(key).getBooleanArray(defaultValue); } /** @@ -670,7 +825,7 @@ public Boolean[] getBooleanArray(String key, Boolean[] defaultValue) { */ @Override public boolean putNumberArray(String key, double[] value) { - return NetworkTablesJNI.putDoubleArray(pathWithSep + key, value); + return getEntry(key).setDoubleArray(value); } /** @@ -678,23 +833,21 @@ public boolean putNumberArray(String key, double[] value) { */ @Override public boolean putNumberArray(String key, Double[] value) { - return putNumberArray(key, toNative(value)); + return getEntry(key).setNumberArray(value); } - + /** * {@inheritDoc} */ public boolean setDefaultNumberArray(String key, double[] defaultValue) { - return NetworkTablesJNI.setDefaultDoubleArray(pathWithSep + key, - defaultValue); + return getEntry(key).setDefaultDoubleArray(defaultValue); } - + /** * {@inheritDoc} */ public boolean setDefaultNumberArray(String key, Double[] defaultValue) { - return NetworkTablesJNI.setDefaultDoubleArray(pathWithSep + key, - toNative(defaultValue)); + return getEntry(key).setDefaultNumberArray(defaultValue); } /** @@ -702,7 +855,7 @@ public boolean setDefaultNumberArray(String key, Double[] defaultValue) { */ @Override public double[] getNumberArray(String key, double[] defaultValue) { - return NetworkTablesJNI.getDoubleArray(pathWithSep + key, defaultValue); + return getEntry(key).getDoubleArray(defaultValue); } /** @@ -710,7 +863,7 @@ public double[] getNumberArray(String key, double[] defaultValue) { */ @Override public Double[] getNumberArray(String key, Double[] defaultValue) { - return fromNative(getNumberArray(key, toNative(defaultValue))); + return getEntry(key).getDoubleArray(defaultValue); } /** @@ -718,23 +871,22 @@ public Double[] getNumberArray(String key, Double[] defaultValue) { */ @Override public boolean putStringArray(String key, String[] value) { - return NetworkTablesJNI.putStringArray(pathWithSep + key, value); + return getEntry(key).setStringArray(value); } - + /** * {@inheritDoc} */ public boolean setDefaultStringArray(String key, String[] defaultValue) { - return NetworkTablesJNI.setDefaultStringArray(pathWithSep + key, - defaultValue); + return getEntry(key).setDefaultStringArray(defaultValue); } - + /** * {@inheritDoc} */ @Override public String[] getStringArray(String key, String[] defaultValue) { - return NetworkTablesJNI.getStringArray(pathWithSep + key, defaultValue); + return getEntry(key).getStringArray(defaultValue); } /** @@ -742,15 +894,14 @@ public String[] getStringArray(String key, String[] defaultValue) { */ @Override public boolean putRaw(String key, byte[] value) { - return NetworkTablesJNI.putRaw(pathWithSep + key, value); + return getEntry(key).setRaw(value); } - + /** * {@inheritDoc} */ public boolean setDefaultRaw(String key, byte[] defaultValue) { - return NetworkTablesJNI.setDefaultRaw(pathWithSep + key, - defaultValue); + return getEntry(key).setDefaultRaw(defaultValue); } /** @@ -758,11 +909,7 @@ public boolean setDefaultRaw(String key, byte[] defaultValue) { */ @Override public boolean putRaw(String key, ByteBuffer value, int len) { - if (!value.isDirect()) - throw new IllegalArgumentException("must be a direct buffer"); - if (value.capacity() < len) - throw new IllegalArgumentException("buffer is too small, must be at least " + len); - return NetworkTablesJNI.putRaw(pathWithSep + key, value, len); + return getEntry(key).setRaw(value, len); } /** @@ -770,42 +917,82 @@ public boolean putRaw(String key, ByteBuffer value, int len) { */ @Override public byte[] getRaw(String key, byte[] defaultValue) { - return NetworkTablesJNI.getRaw(pathWithSep + key, defaultValue); + return getEntry(key).getRaw(defaultValue); + } + + /** + * Put a value in the table + * @param key the key to be assigned to + * @param value the value that will be assigned + * @return False if the table key already exists with a different type + */ + public boolean putValue(String key, NetworkTableValue value) { + return getEntry(key).setValue(value); + } + + /** + * Sets the current value in the table if it does not exist. + * @param key the key + * @param defaultValue the default value to set if key doens't exist. + * @return False if the table key exists with a different type + */ + public boolean setDefaultValue(String key, NetworkTableValue defaultValue) { + return getEntry(key).setDefaultValue(defaultValue); + } + + /** + * Gets the value associated with a key as a NetworkTableValue object. + * @param key the key of the value to look up + * @return the value associated with the given key + */ + public NetworkTableValue getValue(String key) { + return getEntry(key).getValue(); } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTableEntry#setValue(NetworkTableValue)} + * instead, e.g. `NetworkTable.getEntry(key).setValue(NetworkTableEntry.makeBoolean(false));` */ - @Override + @Deprecated public boolean putValue(String key, Object value) throws IllegalArgumentException { if (value instanceof Boolean) - return NetworkTablesJNI.putBoolean(pathWithSep + key, ((Boolean)value).booleanValue()); + return putBoolean(key, ((Boolean)value).booleanValue()); else if (value instanceof Number) - return NetworkTablesJNI.putDouble(pathWithSep + key, ((Number)value).doubleValue()); + return putDouble(key, ((Number)value).doubleValue()); else if (value instanceof String) - return NetworkTablesJNI.putString(pathWithSep + key, (String)value); + return putString(key, (String)value); else if (value instanceof byte[]) - return NetworkTablesJNI.putRaw(pathWithSep + key, (byte[])value); + return putRaw(key, (byte[])value); else if (value instanceof boolean[]) - return NetworkTablesJNI.putBooleanArray(pathWithSep + key, (boolean[])value); + return putBooleanArray(key, (boolean[])value); else if (value instanceof double[]) - return NetworkTablesJNI.putDoubleArray(pathWithSep + key, (double[])value); + return putNumberArray(key, (double[])value); else if (value instanceof Boolean[]) - return NetworkTablesJNI.putBooleanArray(pathWithSep + key, toNative((Boolean[])value)); + return putBooleanArray(key, toNative((Boolean[])value)); else if (value instanceof Number[]) - return NetworkTablesJNI.putDoubleArray(pathWithSep + key, toNative((Number[])value)); + return putNumberArray(key, toNative((Number[])value)); else if (value instanceof String[]) - return NetworkTablesJNI.putStringArray(pathWithSep + key, (String[])value); + return putStringArray(key, (String[])value); + else if (value instanceof NetworkTableValue) + return getEntry(key).setValue((NetworkTableValue)value); else throw new IllegalArgumentException("Value of type " + value.getClass().getName() + " cannot be put into a table"); } /** * {@inheritDoc} + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTableEntry#getValue()} + * instead, e.g. `NetworkTable.getEntry(key).getValue();` */ @Override + @Deprecated public Object getValue(String key, Object defaultValue) { - return NetworkTablesJNI.getValue(pathWithSep + key, defaultValue); + NetworkTableValue value = getValue(key); + if (value.getType() == NetworkTableType.kUnassigned) { + return defaultValue; + } + return value.getValue(); } /** The persistent flag value. */ @@ -816,7 +1003,7 @@ public Object getValue(String key, Object defaultValue) { */ @Override public void setPersistent(String key) { - setFlags(key, PERSISTENT); + getEntry(key).setPersistent(); } /** @@ -824,7 +1011,7 @@ public void setPersistent(String key) { */ @Override public void clearPersistent(String key) { - clearFlags(key, PERSISTENT); + getEntry(key).clearPersistent(); } /** @@ -832,7 +1019,7 @@ public void clearPersistent(String key) { */ @Override public boolean isPersistent(String key) { - return (getFlags(key) & PERSISTENT) != 0; + return getEntry(key).isPersistent(); } /** @@ -840,7 +1027,7 @@ public boolean isPersistent(String key) { */ @Override public void setFlags(String key, int flags) { - NetworkTablesJNI.setEntryFlags(pathWithSep + key, getFlags(key) | flags); + getEntry(key).setFlags(flags); } /** @@ -848,7 +1035,7 @@ public void setFlags(String key, int flags) { */ @Override public void clearFlags(String key, int flags) { - NetworkTablesJNI.setEntryFlags(pathWithSep + key, getFlags(key) & ~flags); + getEntry(key).clearFlags(flags); } /** @@ -856,7 +1043,7 @@ public void clearFlags(String key, int flags) { */ @Override public int getFlags(String key) { - return NetworkTablesJNI.getEntryFlags(pathWithSep + key); + return getEntry(key).getFlags(); } /** @@ -864,14 +1051,16 @@ public int getFlags(String key) { */ @Override public void delete(String key) { - NetworkTablesJNI.deleteEntry(pathWithSep + key); + getEntry(key).delete(); } /** * Deletes ALL keys in ALL subtables. Use with caution! + * @deprecated Use {@link NetworkTableInstance#deleteAllEntries()} instead. */ + @Deprecated public static void globalDeleteAll() { - NetworkTablesJNI.deleteAllEntries(); + NetworkTableInstance.getDefault().deleteAllEntries(); } /** @@ -879,18 +1068,23 @@ public static void globalDeleteAll() { * Note: This is rate-limited to protect the network from flooding. * This is primarily useful for synchronizing network updates with * user code. + * @deprecated Use {@link NetworkTableInstance#flush()} instead. */ + @Deprecated public static void flush() { - NetworkTablesJNI.flush(); + NetworkTableInstance.getDefault().flush(); } /** * Set the periodic update rate. * * @param interval update interval in seconds (range 0.01 to 1.0) + * @deprecated Use {@link NetworkTableInstance#setUpdateRate(double)} + * instead. */ + @Deprecated public static void setUpdateRate(double interval) { - NetworkTablesJNI.setUpdateRate(interval); + NetworkTableInstance.getDefault().setUpdateRate(interval); } /** @@ -898,9 +1092,12 @@ public static void setUpdateRate(double interval) { * * @param filename file name * @throws PersistentException if error saving file + * @deprecated Use {@link NetworkTableInstance#savePersistent(String)} + * instead. */ + @Deprecated public static void savePersistent(String filename) throws PersistentException { - NetworkTablesJNI.savePersistent(filename); + NetworkTableInstance.getDefault().savePersistent(filename); } /** @@ -909,9 +1106,12 @@ public static void savePersistent(String filename) throws PersistentException { * @param filename file name * @return List of warnings (errors result in an exception instead) * @throws PersistentException if error reading file + * @deprecated Use {@link NetworkTableInstance#loadPersistent(String)} + * instead. */ + @Deprecated public static String[] loadPersistent(String filename) throws PersistentException { - return NetworkTablesJNI.loadPersistent(filename); + return NetworkTableInstance.getDefault().loadPersistent(filename); } /* @@ -940,11 +1140,26 @@ public double getDouble(String key, double defaultValue) { /** * {@inheritDoc} - * @return */ @Override public String getPath() { return path; } + @Override + public boolean equals(Object o) { + if (o == this) { + return true; + } + if (!(o instanceof NetworkTable)) { + return false; + } + NetworkTable other = (NetworkTable) o; + return inst.equals(other.inst) && path.equals(other.path); + } + + @Override + public int hashCode() { + return Objects.hash(inst, path); + } } diff --git a/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java b/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java deleted file mode 100644 index c348541..0000000 --- a/src/main/java/edu/wpi/first/wpilibj/networktables/NetworkTablesJNI.java +++ /dev/null @@ -1,159 +0,0 @@ -package edu.wpi.first.wpilibj.networktables; - -import java.io.File; -import java.io.InputStream; -import java.io.OutputStream; -import java.io.FileOutputStream; -import java.io.IOException; -import java.nio.ByteBuffer; -import edu.wpi.first.wpiutil.RuntimeDetector; - -public class NetworkTablesJNI { - static boolean libraryLoaded = false; - static File jniLibrary = null; - static { - if (!libraryLoaded) { - try { - System.loadLibrary("ntcore"); - } catch (UnsatisfiedLinkError e) { - try { - String resname = RuntimeDetector.getLibraryResource("ntcore"); - InputStream is = NetworkTablesJNI.class.getResourceAsStream(resname); - if (is != null) { - // create temporary file - if (System.getProperty("os.name").startsWith("Windows")) - jniLibrary = File.createTempFile("NetworkTablesJNI", ".dll"); - else if (System.getProperty("os.name").startsWith("Mac")) - jniLibrary = File.createTempFile("libNetworkTablesJNI", ".dylib"); - else - jniLibrary = File.createTempFile("libNetworkTablesJNI", ".so"); - // flag for delete on exit - jniLibrary.deleteOnExit(); - OutputStream os = new FileOutputStream(jniLibrary); - - byte[] buffer = new byte[1024]; - int readBytes; - try { - while ((readBytes = is.read(buffer)) != -1) { - os.write(buffer, 0, readBytes); - } - } finally { - os.close(); - is.close(); - } - System.load(jniLibrary.getAbsolutePath()); - } else { - System.loadLibrary("ntcore"); - } - } catch (IOException ex) { - ex.printStackTrace(); - System.exit(1); - } - } - libraryLoaded = true; - } - } - - public static final int NT_NET_MODE_NONE = 0x00; - public static final int NT_NET_MODE_SERVER = 0x01; - public static final int NT_NET_MODE_CLIENT = 0x02; - public static final int NT_NET_MODE_STARTING = 0x04; - public static final int NT_NET_MODE_FAILURE = 0x08; - - public static native boolean containsKey(String key); - public static native int getType(String key); - - public static native boolean putBoolean(String key, boolean value); - public static native boolean putDouble(String key, double value); - public static native boolean putString(String key, String value); - public static native boolean putRaw(String key, byte[] value); - public static native boolean putRaw(String key, ByteBuffer value, int len); - public static native boolean putBooleanArray(String key, boolean[] value); - public static native boolean putDoubleArray(String key, double[] value); - public static native boolean putStringArray(String key, String[] value); - - public static native void forcePutBoolean(String key, boolean value); - public static native void forcePutDouble(String key, double value); - public static native void forcePutString(String key, String value); - public static native void forcePutRaw(String key, byte[] value); - public static native void forcePutRaw(String key, ByteBuffer value, int len); - public static native void forcePutBooleanArray(String key, boolean[] value); - public static native void forcePutDoubleArray(String key, double[] value); - public static native void forcePutStringArray(String key, String[] value); - - public static native Object getValue(String key, Object defaultValue); - public static native boolean getBoolean(String key, boolean defaultValue); - public static native double getDouble(String key, double defaultValue); - public static native String getString(String key, String defaultValue); - public static native byte[] getRaw(String key, byte[] defaultValue); - public static native boolean[] getBooleanArray(String key, boolean[] defaultValue); - public static native double[] getDoubleArray(String key, double[] defaultValue); - public static native String[] getStringArray(String key, String[] defaultValue); - - public static native boolean setDefaultBoolean(String key, boolean defaultValue); - public static native boolean setDefaultDouble(String key, double defaultValue); - public static native boolean setDefaultString(String key, String defaultValue); - public static native boolean setDefaultRaw(String key, byte[] defaultValue); - public static native boolean setDefaultBooleanArray(String key, boolean[] defaultValue); - public static native boolean setDefaultDoubleArray(String key, double[] defaultValue); - public static native boolean setDefaultStringArray(String key, String[] defaultValue); - - public static native void setEntryFlags(String key, int flags); - public static native int getEntryFlags(String key); - - public static native void deleteEntry(String key); - public static native void deleteAllEntries(); - - public static native EntryInfo[] getEntries(String prefix, int types); - - public static native void flush(); - - @FunctionalInterface - public interface EntryListenerFunction { - void apply(int uid, String key, Object value, int flags); - } - public static native int addEntryListener(String prefix, EntryListenerFunction listener, int flags); - public static native void removeEntryListener(int entryListenerUid); - - @FunctionalInterface - public interface ConnectionListenerFunction { - void apply(int uid, boolean connected, ConnectionInfo conn); - } - public static native int addConnectionListener(ConnectionListenerFunction listener, boolean immediateNotify); - public static native void removeConnectionListener(int connListenerUid); - - // public static native void createRpc(String key, byte[] def, IRpc rpc); - // public static native void createRpc(String key, ByteBuffer def, int def_len, IRpc rpc); - public static native byte[] getRpc(String key, byte[] defaultValue); - public static native int callRpc(String key, byte[] params); - public static native int callRpc(String key, ByteBuffer params, int params_len); - // public static native byte[] getRpcResultBlocking(int callUid); - // public static native byte[] getRpcResultNonblocking(int callUid) throws RpcNoResponseException; - - public static native void setNetworkIdentity(String name); - public static native int getNetworkMode(); - public static native void startServer(String persistFilename, String listenAddress, int port); - public static native void stopServer(); - public static native void startClient(); - public static native void startClient(String serverName, int port); - public static native void startClient(String[] serverNames, int[] ports); - public static native void stopClient(); - public static native void setServer(String serverName, int port); - public static native void setServer(String[] serverNames, int[] ports); - public static native void startDSClient(int port); - public static native void stopDSClient(); - public static native void setUpdateRate(double interval); - - public static native ConnectionInfo[] getConnections(); - - public static native void savePersistent(String filename) throws PersistentException; - public static native String[] loadPersistent(String filename) throws PersistentException; // returns warnings - - public static native long now(); - - @FunctionalInterface - public interface LoggerFunction { - void apply(int level, String file, int line, String msg); - } - public static native void setLogger(LoggerFunction func, int minLevel); -} diff --git a/src/main/java/edu/wpi/first/wpilibj/networktables/PersistentException.java b/src/main/java/edu/wpi/first/wpilibj/networktables/PersistentException.java deleted file mode 100644 index 8d521fb..0000000 --- a/src/main/java/edu/wpi/first/wpilibj/networktables/PersistentException.java +++ /dev/null @@ -1,18 +0,0 @@ -package edu.wpi.first.wpilibj.networktables; - -import java.io.IOException; - -/** - * An exception thrown when persistent load/save fails in a {@link NetworkTable} - * - */ -public class PersistentException extends IOException { - - /** - * @param message The error message - */ - public PersistentException(String message) { - super(message); - } - -} diff --git a/src/main/java/edu/wpi/first/wpilibj/tables/IRemote.java b/src/main/java/edu/wpi/first/wpilibj/tables/IRemote.java index a79f740..52ab5ee 100644 --- a/src/main/java/edu/wpi/first/wpilibj/tables/IRemote.java +++ b/src/main/java/edu/wpi/first/wpilibj/tables/IRemote.java @@ -3,14 +3,13 @@ /** * Represents an object that has a remote connection - * - * @author Mitchell - * + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTableInstance}. */ +@Deprecated public interface IRemote { /** * Register an object to listen for connection and disconnection events - * + * * @param listener the listener to be register * @param immediateNotify if the listener object should be notified of the current connection state */ @@ -18,17 +17,17 @@ public interface IRemote { /** * Unregister a listener from connection events - * + * * @param listener the listener to be unregistered */ public void removeConnectionListener(IRemoteConnectionListener listener); - + /** * Get the current state of the objects connection * @return the current connection state */ public boolean isConnected(); - + /** * If the object is acting as a server * @return if the object is a server diff --git a/src/main/java/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java b/src/main/java/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java index 9ca0e5f..fcb884e 100644 --- a/src/main/java/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java +++ b/src/main/java/edu/wpi/first/wpilibj/tables/IRemoteConnectionListener.java @@ -1,13 +1,12 @@ package edu.wpi.first.wpilibj.tables; -import edu.wpi.first.wpilibj.networktables.ConnectionInfo; +import edu.wpi.first.networktables.ConnectionInfo; /** * A listener that listens for connection changes in a {@link IRemote} object - * - * @author Mitchell - * + * @deprecated Use Consumer<{@link edu.wpi.first.networktables.ConnectionNotification}>. */ +@Deprecated public interface IRemoteConnectionListener { /** * Called when an IRemote is connected diff --git a/src/main/java/edu/wpi/first/wpilibj/tables/ITable.java b/src/main/java/edu/wpi/first/wpilibj/tables/ITable.java index eeb31e7..86311cd 100644 --- a/src/main/java/edu/wpi/first/wpilibj/tables/ITable.java +++ b/src/main/java/edu/wpi/first/wpilibj/tables/ITable.java @@ -6,7 +6,9 @@ /** * A table whose values can be read and written to + * @deprecated Use {@link edu.wpi.first.networktables.NetworkTable}. */ +@Deprecated public interface ITable { /** @@ -34,17 +36,20 @@ public interface ITable { public ITable getSubTable(String key); /** + * Gets all keys in the table (not including sub-tables). * @param types bitmask of types; 0 is treated as a "don't care". * @return keys currently in the table */ public Set getKeys(int types); /** + * Gets all keys in the table (not including sub-tables). * @return keys currently in the table */ public Set getKeys(); /** + * Gets the names of all subtables in the table. * @return subtables currently in the table */ public Set getSubTables(); @@ -137,15 +142,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putNumber(String key, double value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultNumber(String key, double defaultValue); - + /** * Returns the number the key maps to. If the key does not exist or is of * different type, it will return the default value. @@ -163,15 +168,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putString(String key, String value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultString(String key, String defaultValue); - + /** * Returns the string the key maps to. If the key does not exist or is of * different type, it will return the default value. @@ -189,15 +194,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putBoolean(String key, boolean value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultBoolean(String key, boolean defaultValue); - + /** * Returns the boolean the key maps to. If the key does not exist or is of * different type, it will return the default value. @@ -215,15 +220,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putBooleanArray(String key, boolean[] value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultBooleanArray(String key, boolean[] defaultValue); - + /** * Put a boolean array in the table * @param key the key to be assigned to @@ -231,15 +236,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putBooleanArray(String key, Boolean[] value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultBooleanArray(String key, Boolean[] defaultValue); - + /** * Returns the boolean array the key maps to. If the key does not exist or is * of different type, it will return the default value. @@ -266,15 +271,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putNumberArray(String key, double[] value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultNumberArray(String key, double[] defaultValue); - + /** * Put a number array in the table * @param key the key to be assigned to @@ -282,15 +287,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putNumberArray(String key, Double[] value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultNumberArray(String key, Double[] defaultValue); - + /** * Returns the number array the key maps to. If the key does not exist or is * of different type, it will return the default value. @@ -317,15 +322,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putStringArray(String key, String[] value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultStringArray(String key, String[] defaultValue); - + /** * Returns the string array the key maps to. If the key does not exist or is * of different type, it will return the default value. @@ -343,15 +348,15 @@ public boolean putValue(String key, Object value) * @return False if the table key already exists with a different type */ public boolean putRaw(String key, byte[] value); - - /** + + /** * Gets the current value in the table, setting it if it does not exist. * @param key the key * @param defaultValue the default value to set if key doens't exist. * @return False if the table key exists with a different type */ public boolean setDefaultRaw(String key, byte[] defaultValue); - + /** * Put a raw value (bytes from a byte buffer) in the table * @param key the key to be assigned to @@ -467,7 +472,8 @@ public void addSubTableListener(final ITableListener listener, public double getDouble(String key, double defaultValue); /** - * Gets the full path of this table. + * Gets the full path of this table. Does not include the trailing "/". + * @return The path to this table (e.g. "", "/foo"). */ public String getPath(); diff --git a/src/main/java/edu/wpi/first/wpilibj/tables/ITableListener.java b/src/main/java/edu/wpi/first/wpilibj/tables/ITableListener.java index e1d9259..9b1304b 100644 --- a/src/main/java/edu/wpi/first/wpilibj/tables/ITableListener.java +++ b/src/main/java/edu/wpi/first/wpilibj/tables/ITableListener.java @@ -2,11 +2,12 @@ /** * A listener that listens to changes in values in a {@link ITable} - * - * @author Mitchell - * + * @deprecated Use Consumer<{@link edu.wpi.first.networktables.EntryNotification}>, + * {@link edu.wpi.first.networktables.TableEntryListener}, or + * {@link edu.wpi.first.networktables.TableListener} as appropriate. */ @FunctionalInterface +@Deprecated public interface ITableListener { /** * Called when a key-value pair is changed in a {@link ITable} diff --git a/src/main/native/cpp/CallbackManager.h b/src/main/native/cpp/CallbackManager.h new file mode 100644 index 0000000..ff4a095 --- /dev/null +++ b/src/main/native/cpp/CallbackManager.h @@ -0,0 +1,338 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_CALLBACKMANAGER_H_ +#define NT_CALLBACKMANAGER_H_ + +#include +#include +#include +#include +#include +#include +#include + +#include "llvm/raw_ostream.h" +#include "support/SafeThread.h" +#include "support/UidVector.h" + +namespace nt { + +namespace impl { + +template +class ListenerData { + public: + ListenerData() = default; + ListenerData(Callback callback_) : callback(callback_) {} + ListenerData(unsigned int poller_uid_) : poller_uid(poller_uid_) {} + + explicit operator bool() const { return callback || poller_uid != UINT_MAX; } + + Callback callback; + unsigned int poller_uid = UINT_MAX; +}; + +// CRTP callback manager thread +// @tparam Derived derived class +// @tparam NotifierData data buffered for each callback +// @tparam ListenerData data stored for each listener +// Derived must define the following functions: +// bool Matches(const ListenerData& listener, const NotifierData& data); +// void SetListener(NotifierData* data, unsigned int listener_uid); +// void DoCallback(Callback callback, const NotifierData& data); +template >, + typename TNotifierData = TUserInfo> +class CallbackThread : public wpi::SafeThread { + public: + typedef TUserInfo UserInfo; + typedef TNotifierData NotifierData; + typedef TListenerData ListenerData; + + ~CallbackThread() { + // Wake up any blocked pollers + for (std::size_t i = 0; i < m_pollers.size(); ++i) { + if (auto poller = m_pollers[i]) poller->Terminate(); + } + } + + void Main() override; + + wpi::UidVector m_listeners; + + std::queue> m_queue; + std::condition_variable m_queue_empty; + + struct Poller { + void Terminate() { + { + std::lock_guard lock(poll_mutex); + terminating = true; + } + poll_cond.notify_all(); + } + std::queue poll_queue; + std::mutex poll_mutex; + std::condition_variable poll_cond; + bool terminating = false; + bool cancelling = false; + }; + wpi::UidVector, 64> m_pollers; + + // Must be called with m_mutex held + template + void SendPoller(unsigned int poller_uid, Args&&... args) { + if (poller_uid > m_pollers.size()) return; + auto poller = m_pollers[poller_uid]; + if (!poller) return; + { + std::lock_guard lock(poller->poll_mutex); + poller->poll_queue.emplace(std::forward(args)...); + } + poller->poll_cond.notify_one(); + } +}; + +template +void CallbackThread::Main() { + std::unique_lock lock(m_mutex); + while (m_active) { + while (m_queue.empty()) { + m_cond.wait(lock); + if (!m_active) return; + } + + while (!m_queue.empty()) { + if (!m_active) return; + auto item = std::move(m_queue.front()); + + if (item.first != UINT_MAX) { + if (item.first < m_listeners.size()) { + auto& listener = m_listeners[item.first]; + if (listener && + static_cast(this)->Matches(listener, item.second)) { + static_cast(this)->SetListener(&item.second, item.first); + if (listener.callback) { + lock.unlock(); + static_cast(this)->DoCallback(listener.callback, + item.second); + lock.lock(); + } else if (listener.poller_uid != UINT_MAX) { + SendPoller(listener.poller_uid, std::move(item.second)); + } + } + } + } else { + // Use index because iterator might get invalidated. + for (size_t i = 0; i < m_listeners.size(); ++i) { + auto& listener = m_listeners[i]; + if (!listener) continue; + if (!static_cast(this)->Matches(listener, item.second)) + continue; + static_cast(this)->SetListener(&item.second, i); + if (listener.callback) { + lock.unlock(); + static_cast(this)->DoCallback(listener.callback, + item.second); + lock.lock(); + } else if (listener.poller_uid != UINT_MAX) { + SendPoller(listener.poller_uid, item.second); + } + } + } + m_queue.pop(); + } + + m_queue_empty.notify_all(); + } +} + +} // namespace impl + +// CRTP callback manager +// @tparam Derived derived class +// @tparam Thread custom thread (must be derived from impl::CallbackThread) +// +// Derived must define the following functions: +// void Start(); +template +class CallbackManager { + friend class RpcServerTest; + + public: + void Stop() { m_owner.Stop(); } + + void Remove(unsigned int listener_uid) { + auto thr = m_owner.GetThread(); + if (!thr) return; + thr->m_listeners.erase(listener_uid); + } + + unsigned int CreatePoller() { + static_cast(this)->Start(); + auto thr = m_owner.GetThread(); + return thr->m_pollers.emplace_back( + std::make_shared()); + } + + void RemovePoller(unsigned int poller_uid) { + auto thr = m_owner.GetThread(); + if (!thr) return; + + // Remove any listeners that are associated with this poller + for (std::size_t i = 0; i < thr->m_listeners.size(); ++i) { + if (thr->m_listeners[i].poller_uid == poller_uid) + thr->m_listeners.erase(i); + } + + // Wake up any blocked pollers + if (poller_uid >= thr->m_pollers.size()) return; + auto poller = thr->m_pollers[poller_uid]; + if (!poller) return; + poller->Terminate(); + return thr->m_pollers.erase(poller_uid); + } + + bool WaitForQueue(double timeout) { + auto thr = m_owner.GetThread(); + if (!thr) return true; + + auto& lock = thr.GetLock(); +#if defined(_MSC_VER) && _MSC_VER < 1900 + auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::duration( + static_cast(timeout * 1e9)); +#else + auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::duration(timeout); +#endif + while (!thr->m_queue.empty()) { + if (!thr->m_active) return true; + if (timeout == 0) return false; + if (timeout < 0) { + thr->m_queue_empty.wait(lock); + } else { + auto cond_timed_out = thr->m_queue_empty.wait_until(lock, timeout_time); + if (cond_timed_out == std::cv_status::timeout) return false; + } + } + + return true; + } + + std::vector Poll(unsigned int poller_uid) { + bool timed_out = false; + return Poll(poller_uid, -1, &timed_out); + } + + std::vector Poll(unsigned int poller_uid, + double timeout, bool* timed_out) { + std::vector infos; + std::shared_ptr poller; + { + auto thr = m_owner.GetThread(); + if (!thr) return infos; + if (poller_uid > thr->m_pollers.size()) return infos; + poller = thr->m_pollers[poller_uid]; + if (!poller) return infos; + } + + std::unique_lock lock(poller->poll_mutex); +#if defined(_MSC_VER) && _MSC_VER < 1900 + auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::duration( + static_cast(timeout * 1e9)); +#else + auto timeout_time = std::chrono::steady_clock::now() + + std::chrono::duration(timeout); +#endif + *timed_out = false; + while (poller->poll_queue.empty()) { + if (poller->terminating) return infos; + if (poller->cancelling) { + // Note: this only works if there's a single thread calling this + // function for any particular poller, but that's the intended use. + poller->cancelling = false; + return infos; + } + if (timeout == 0) { + *timed_out = true; + return infos; + } + if (timeout < 0) { + poller->poll_cond.wait(lock); + } else { + auto cond_timed_out = poller->poll_cond.wait_until(lock, timeout_time); + if (cond_timed_out == std::cv_status::timeout) { + *timed_out = true; + return infos; + } + } + } + + while (!poller->poll_queue.empty()) { + infos.emplace_back(std::move(poller->poll_queue.front())); + poller->poll_queue.pop(); + } + return infos; + } + + void CancelPoll(unsigned int poller_uid) { + std::shared_ptr poller; + { + auto thr = m_owner.GetThread(); + if (!thr) return; + if (poller_uid > thr->m_pollers.size()) return; + poller = thr->m_pollers[poller_uid]; + if (!poller) return; + } + + { + std::lock_guard lock(poller->poll_mutex); + poller->cancelling = true; + } + poller->poll_cond.notify_one(); + } + + protected: + template + void DoStart(Args&&... args) { + auto thr = m_owner.GetThread(); + if (!thr) m_owner.Start(new Thread(std::forward(args)...)); + } + + template + unsigned int DoAdd(Args&&... args) { + static_cast(this)->Start(); + auto thr = m_owner.GetThread(); + return thr->m_listeners.emplace_back(std::forward(args)...); + } + + template + void Send(unsigned int only_listener, Args&&... args) { + auto thr = m_owner.GetThread(); + if (!thr || thr->m_listeners.empty()) return; + thr->m_queue.emplace(std::piecewise_construct, + std::make_tuple(only_listener), + std::forward_as_tuple(std::forward(args)...)); + thr->m_cond.notify_one(); + } + + typename wpi::SafeThreadOwner::Proxy GetThread() const { + return m_owner.GetThread(); + } + + private: + wpi::SafeThreadOwner m_owner; +}; + +} // namespace nt + +#endif // NT_CALLBACKMANAGER_H_ diff --git a/src/main/native/cpp/ConnectionNotifier.cpp b/src/main/native/cpp/ConnectionNotifier.cpp new file mode 100644 index 0000000..e3a6284 --- /dev/null +++ b/src/main/native/cpp/ConnectionNotifier.cpp @@ -0,0 +1,29 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "ConnectionNotifier.h" + +using namespace nt; + +ConnectionNotifier::ConnectionNotifier(int inst) : m_inst(inst) {} + +void ConnectionNotifier::Start() { DoStart(m_inst); } + +unsigned int ConnectionNotifier::Add( + std::function callback) { + return DoAdd(callback); +} + +unsigned int ConnectionNotifier::AddPolled(unsigned int poller_uid) { + return DoAdd(poller_uid); +} + +void ConnectionNotifier::NotifyConnection(bool connected, + const ConnectionInfo& conn_info, + unsigned int only_listener) { + Send(only_listener, 0, connected, conn_info); +} diff --git a/src/main/native/cpp/ConnectionNotifier.h b/src/main/native/cpp/ConnectionNotifier.h new file mode 100644 index 0000000..86457a7 --- /dev/null +++ b/src/main/native/cpp/ConnectionNotifier.h @@ -0,0 +1,73 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_CONNECTIONNOTIFIER_H_ +#define NT_CONNECTIONNOTIFIER_H_ + +#include "ntcore_cpp.h" + +#include "CallbackManager.h" +#include "Handle.h" +#include "IConnectionNotifier.h" + +namespace nt { + +namespace impl { + +class ConnectionNotifierThread + : public CallbackThread { + public: + ConnectionNotifierThread(int inst) : m_inst(inst) {} + + bool Matches(const ListenerData& listener, + const ConnectionNotification& data) { + return true; + } + + void SetListener(ConnectionNotification* data, unsigned int listener_uid) { + data->listener = + Handle(m_inst, listener_uid, Handle::kConnectionListener).handle(); + } + + void DoCallback( + std::function callback, + const ConnectionNotification& data) { + callback(data); + } + + int m_inst; +}; + +} // namespace impl + +class ConnectionNotifier + : public IConnectionNotifier, + public CallbackManager { + friend class ConnectionNotifierTest; + friend class CallbackManager; + + public: + explicit ConnectionNotifier(int inst); + + void Start(); + + unsigned int Add( + std::function callback); + unsigned int AddPolled(unsigned int poller_uid); + + void NotifyConnection(bool connected, const ConnectionInfo& conn_info, + unsigned int only_listener = UINT_MAX) override; + + private: + int m_inst; +}; + +} // namespace nt + +#endif // NT_CONNECTIONNOTIFIER_H_ diff --git a/src/main/native/cpp/Dispatcher.cpp b/src/main/native/cpp/Dispatcher.cpp index 7df1314..9984944 100644 --- a/src/main/native/cpp/Dispatcher.cpp +++ b/src/main/native/cpp/Dispatcher.cpp @@ -10,28 +10,28 @@ #include #include -#include "Log.h" #include "tcpsockets/TCPAcceptor.h" #include "tcpsockets/TCPConnector.h" -using namespace nt; +#include "IConnectionNotifier.h" +#include "Log.h" +#include "IStorage.h" -ATOMIC_STATIC_INIT(Dispatcher) +using namespace nt; void Dispatcher::StartServer(llvm::StringRef persist_filename, const char* listen_address, unsigned int port) { DispatcherBase::StartServer( persist_filename, std::unique_ptr(new wpi::TCPAcceptor( - static_cast(port), listen_address, Logger::GetInstance()))); + static_cast(port), listen_address, m_logger))); } void Dispatcher::SetServer(const char* server_name, unsigned int port) { std::string server_name_copy(server_name); SetConnector([=]() -> std::unique_ptr { return wpi::TCPConnector::connect(server_name_copy.c_str(), - static_cast(port), - Logger::GetInstance(), 1); + static_cast(port), m_logger, 1); }); } @@ -46,39 +46,72 @@ void Dispatcher::SetServer( llvm::SmallVector, 16> servers_copy2; for (const auto& server : servers_copy) servers_copy2.emplace_back(server.first.c_str(), server.second); - return wpi::TCPConnector::connect_parallel(servers_copy2, - Logger::GetInstance(), 1); + return wpi::TCPConnector::connect_parallel(servers_copy2, m_logger, 1); }); } +void Dispatcher::SetServerTeam(unsigned int team, unsigned int port) { + std::pair servers[5]; + + // 10.te.am.2 + llvm::SmallString<32> fixed; + { + llvm::raw_svector_ostream oss{fixed}; + oss << "10." << static_cast(team / 100) << '.' + << static_cast(team % 100) << ".2"; + servers[0] = std::make_pair(oss.str(), port); + } + + // 172.22.11.2 + servers[1] = std::make_pair("172.22.11.2", port); + + // roboRIO--FRC.local + llvm::SmallString<32> mdns; + { + llvm::raw_svector_ostream oss{mdns}; + oss << "roboRIO-" << team << "-FRC.local"; + servers[2] = std::make_pair(oss.str(), port); + } + + // roboRIO--FRC.lan + llvm::SmallString<32> mdns_lan; + { + llvm::raw_svector_ostream oss{mdns_lan}; + oss << "roboRIO-" << team << "-FRC.lan"; + servers[3] = std::make_pair(oss.str(), port); + } + + // roboRIO--FRC.frc-field.local + llvm::SmallString<64> field_local; + { + llvm::raw_svector_ostream oss{field_local}; + oss << "roboRIO-" << team << "-FRC.frc-field.local"; + servers[4] = std::make_pair(oss.str(), port); + } + + SetServer(servers); +} + void Dispatcher::SetServerOverride(const char* server_name, unsigned int port) { std::string server_name_copy(server_name); SetConnectorOverride([=]() -> std::unique_ptr { return wpi::TCPConnector::connect(server_name_copy.c_str(), - static_cast(port), - Logger::GetInstance(), 1); + static_cast(port), m_logger, 1); }); } void Dispatcher::ClearServerOverride() { ClearConnectorOverride(); } -Dispatcher::Dispatcher() - : Dispatcher(Storage::GetInstance(), Notifier::GetInstance()) {} - -DispatcherBase::DispatcherBase(Storage& storage, Notifier& notifier) - : m_storage(storage), m_notifier(notifier) { +DispatcherBase::DispatcherBase(IStorage& storage, IConnectionNotifier& notifier, + wpi::Logger& logger) + : m_storage(storage), m_notifier(notifier), m_logger(logger) { m_active = false; m_update_rate = 100; } -DispatcherBase::~DispatcherBase() { - Logger::GetInstance().SetLogger(nullptr); - Stop(); -} +DispatcherBase::~DispatcherBase() { Stop(); } -unsigned int DispatcherBase::GetNetworkMode() const { - return m_networkMode; -} +unsigned int DispatcherBase::GetNetworkMode() const { return m_networkMode; } void DispatcherBase::StartServer( StringRef persist_filename, @@ -106,9 +139,7 @@ void DispatcherBase::StartServer( }); } - using namespace std::placeholders; - m_storage.SetOutgoing(std::bind(&Dispatcher::QueueOutgoing, this, _1, _2, _3), - (m_networkMode & NT_NET_MODE_SERVER) != 0); + m_storage.SetDispatcher(this, true); m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this); m_clientserver_thread = std::thread(&Dispatcher::ServerThreadMain, this); @@ -121,9 +152,7 @@ void DispatcherBase::StartClient() { m_active = true; } m_networkMode = NT_NET_MODE_CLIENT | NT_NET_MODE_STARTING; - using namespace std::placeholders; - m_storage.SetOutgoing(std::bind(&Dispatcher::QueueOutgoing, this, _1, _2, _3), - (m_networkMode & NT_NET_MODE_SERVER) != 0); + m_storage.SetDispatcher(this, false); m_dispatch_thread = std::thread(&Dispatcher::DispatchThreadMain, this); m_clientserver_thread = std::thread(&Dispatcher::ClientThreadMain, this); @@ -198,10 +227,15 @@ std::vector DispatcherBase::GetConnections() const { return conns; } -void DispatcherBase::NotifyConnections( - ConnectionListenerCallback callback) const { +bool DispatcherBase::IsConnected() const { + if (!m_active) return false; + std::lock_guard lock(m_user_mutex); - for (const auto& conn : m_connections) conn->NotifyIfActive(callback); + for (auto& conn : m_connections) { + if (conn->state() == NetworkConnection::kActive) return true; + } + + return false; } void DispatcherBase::SetConnector(Connector connector) { @@ -242,7 +276,8 @@ void DispatcherBase::DispatchThreadMain() { if (!m_active) break; // in case we were woken up to terminate // perform periodic persistent save - if ((m_networkMode & NT_NET_MODE_SERVER) != 0 && !m_persist_filename.empty() && start > next_save_time) { + if ((m_networkMode & NT_NET_MODE_SERVER) != 0 && + !m_persist_filename.empty() && start > next_save_time) { next_save_time += save_delta_time; // handle loop taking too long if (start > next_save_time) next_save_time = start + save_delta_time; @@ -266,7 +301,8 @@ void DispatcherBase::DispatchThreadMain() { conn->PostOutgoing((m_networkMode & NT_NET_MODE_CLIENT) != 0); // if client, reconnect if connection died - if ((m_networkMode & NT_NET_MODE_CLIENT) != 0 && conn->state() == NetworkConnection::kDead) + if ((m_networkMode & NT_NET_MODE_CLIENT) != 0 && + conn->state() == NetworkConnection::kDead) reconnect = true; } // reconnect if we disconnected (and a reconnect is not in progress) @@ -316,11 +352,11 @@ void DispatcherBase::ServerThreadMain() { // add to connections list using namespace std::placeholders; auto conn = std::make_shared( - std::move(stream), m_notifier, + ++m_connections_uid, std::move(stream), m_notifier, m_logger, std::bind(&Dispatcher::ServerHandshake, this, _1, _2, _3), - std::bind(&Storage::GetEntryType, &m_storage, _1)); + std::bind(&IStorage::GetMessageEntryType, &m_storage, _1)); conn->set_process_incoming( - std::bind(&Storage::ProcessIncoming, &m_storage, _1, _2, + std::bind(&IStorage::ProcessIncoming, &m_storage, _1, _2, std::weak_ptr(conn))); { std::lock_guard lock(m_user_mutex); @@ -373,11 +409,11 @@ void DispatcherBase::ClientThreadMain() { std::unique_lock lock(m_user_mutex); using namespace std::placeholders; auto conn = std::make_shared( - std::move(stream), m_notifier, + ++m_connections_uid, std::move(stream), m_notifier, m_logger, std::bind(&Dispatcher::ClientHandshake, this, _1, _2, _3), - std::bind(&Storage::GetEntryType, &m_storage, _1)); + std::bind(&IStorage::GetMessageEntryType, &m_storage, _1)); conn->set_process_incoming( - std::bind(&Storage::ProcessIncoming, &m_storage, _1, _2, + std::bind(&IStorage::ProcessIncoming, &m_storage, _1, _2, std::weak_ptr(conn))); m_connections.resize(0); // disconnect any current m_connections.emplace_back(conn); diff --git a/src/main/native/cpp/Dispatcher.h b/src/main/native/cpp/Dispatcher.h index 5f3cf13..3d4151b 100644 --- a/src/main/native/cpp/Dispatcher.h +++ b/src/main/native/cpp/Dispatcher.h @@ -19,24 +19,28 @@ #include "llvm/StringRef.h" -#include "support/atomic_static.h" +#include "IDispatcher.h" #include "NetworkConnection.h" -#include "Notifier.h" -#include "Storage.h" namespace wpi { +class Logger; class NetworkAcceptor; class NetworkStream; } namespace nt { -class DispatcherBase { +class IConnectionNotifier; +class IStorage; + +class DispatcherBase : public IDispatcher { friend class DispatcherTest; public: typedef std::function()> Connector; + DispatcherBase(IStorage& storage, IConnectionNotifier& notifier, + wpi::Logger& logger); virtual ~DispatcherBase(); unsigned int GetNetworkMode() const; @@ -48,7 +52,7 @@ class DispatcherBase { void SetIdentity(llvm::StringRef name); void Flush(); std::vector GetConnections() const; - void NotifyConnections(ConnectionListenerCallback callback) const; + bool IsConnected() const; void SetConnector(Connector connector); void SetConnectorOverride(Connector connector); @@ -59,9 +63,6 @@ class DispatcherBase { DispatcherBase(const DispatcherBase&) = delete; DispatcherBase& operator=(const DispatcherBase&) = delete; - protected: - DispatcherBase(Storage& storage, Notifier& notifier); - private: void DispatchThreadMain(); void ServerThreadMain(); @@ -79,10 +80,10 @@ class DispatcherBase { void ClientReconnect(unsigned int proto_rev = 0x0300); void QueueOutgoing(std::shared_ptr msg, NetworkConnection* only, - NetworkConnection* except); + NetworkConnection* except) override; - Storage& m_storage; - Notifier& m_notifier; + IStorage& m_storage; + IConnectionNotifier& m_notifier; unsigned int m_networkMode = NT_NET_MODE_NONE; std::string m_persist_filename; std::thread m_dispatch_thread; @@ -91,6 +92,7 @@ class DispatcherBase { std::unique_ptr m_server_acceptor; Connector m_client_connector_override; Connector m_client_connector; + uint8_t m_connections_uid = 0; // Mutex for user-accessible items mutable std::mutex m_user_mutex; @@ -110,32 +112,28 @@ class DispatcherBase { std::condition_variable m_reconnect_cv; unsigned int m_reconnect_proto_rev = 0x0300; bool m_do_reconnect = true; + + protected: + wpi::Logger& m_logger; }; class Dispatcher : public DispatcherBase { friend class DispatcherTest; public: - static Dispatcher& GetInstance() { - ATOMIC_STATIC(Dispatcher, instance); - return instance; - } + Dispatcher(IStorage& storage, IConnectionNotifier& notifier, + wpi::Logger& logger) + : DispatcherBase(storage, notifier, logger) {} void StartServer(StringRef persist_filename, const char* listen_address, unsigned int port); void SetServer(const char* server_name, unsigned int port); void SetServer(ArrayRef> servers); + void SetServerTeam(unsigned int team, unsigned int port); void SetServerOverride(const char* server_name, unsigned int port); void ClearServerOverride(); - - private: - Dispatcher(); - Dispatcher(Storage& storage, Notifier& notifier) - : DispatcherBase(storage, notifier) {} - - ATOMIC_STATIC_DECL(Dispatcher) }; } // namespace nt diff --git a/src/main/native/cpp/DsClient.cpp b/src/main/native/cpp/DsClient.cpp index 1e2c5c2..b8e8c00 100644 --- a/src/main/native/cpp/DsClient.cpp +++ b/src/main/native/cpp/DsClient.cpp @@ -17,22 +17,26 @@ using namespace nt; -ATOMIC_STATIC_INIT(DsClient) - class DsClient::Thread : public wpi::SafeThread { public: - Thread(unsigned int port) : m_port(port) {} + Thread(Dispatcher& dispatcher, wpi::Logger& logger, unsigned int port) + : m_dispatcher(dispatcher), m_logger(logger), m_port(port) {} void Main(); + Dispatcher& m_dispatcher; + wpi::Logger& m_logger; unsigned int m_port; std::unique_ptr m_stream; }; +DsClient::DsClient(Dispatcher& dispatcher, wpi::Logger& logger) + : m_dispatcher(dispatcher), m_logger(logger) {} + void DsClient::Start(unsigned int port) { auto thr = m_owner.GetThread(); if (!thr) - m_owner.Start(new Thread(port)); + m_owner.Start(new Thread(m_dispatcher, m_logger, port)); else thr->m_port = port; } @@ -122,7 +126,7 @@ void DsClient::Thread::Main() { // If zero, clear the server override if (ip == 0) { - Dispatcher::GetInstance().ClearServerOverride(); + m_dispatcher.ClearServerOverride(); oldip = 0; continue; } @@ -137,14 +141,14 @@ void DsClient::Thread::Main() { os << ((ip >> 24) & 0xff) << "." << ((ip >> 16) & 0xff) << "." << ((ip >> 8) & 0xff) << "." << (ip & 0xff); INFO("client: DS overriding server IP to " << os.str()); - Dispatcher::GetInstance().SetServerOverride(json.c_str(), port); + m_dispatcher.SetServerOverride(json.c_str(), port); } // We disconnected from the DS, clear the server override - Dispatcher::GetInstance().ClearServerOverride(); + m_dispatcher.ClearServerOverride(); oldip = 0; } done: - Dispatcher::GetInstance().ClearServerOverride(); + m_dispatcher.ClearServerOverride(); } diff --git a/src/main/native/cpp/DsClient.h b/src/main/native/cpp/DsClient.h index 9dd905c..fdf1f39 100644 --- a/src/main/native/cpp/DsClient.h +++ b/src/main/native/cpp/DsClient.h @@ -8,29 +8,27 @@ #ifndef NT_DSCLIENT_H_ #define NT_DSCLIENT_H_ -#include "support/atomic_static.h" #include "support/SafeThread.h" +#include "Log.h" + namespace nt { +class Dispatcher; + class DsClient { public: - static DsClient& GetInstance() { - ATOMIC_STATIC(DsClient, instance); - return instance; - } + DsClient(Dispatcher& dispatcher, wpi::Logger& logger); ~DsClient() = default; void Start(unsigned int port); void Stop(); private: - DsClient() = default; - class Thread; wpi::SafeThreadOwner m_owner; - - ATOMIC_STATIC_DECL(DsClient) + Dispatcher& m_dispatcher; + wpi::Logger& m_logger; }; } // namespace nt diff --git a/src/main/native/cpp/EntryNotifier.cpp b/src/main/native/cpp/EntryNotifier.cpp new file mode 100644 index 0000000..1cd2441 --- /dev/null +++ b/src/main/native/cpp/EntryNotifier.cpp @@ -0,0 +1,89 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "EntryNotifier.h" + +#include "Log.h" + +using namespace nt; + +EntryNotifier::EntryNotifier(int inst, wpi::Logger& logger) + : m_inst(inst), m_logger(logger) { + m_local_notifiers = false; +} + +void EntryNotifier::Start() { DoStart(m_inst); } + +bool EntryNotifier::local_notifiers() const { return m_local_notifiers; } + +bool impl::EntryNotifierThread::Matches(const EntryListenerData& listener, + const EntryNotification& data) { + if (!data.value) return false; + + // Flags must be within requested flag set for this listener. + // Because assign messages can result in both a value and flags update, + // we handle that case specially. + unsigned int listen_flags = + listener.flags & ~(NT_NOTIFY_IMMEDIATE | NT_NOTIFY_LOCAL); + unsigned int flags = data.flags & ~(NT_NOTIFY_IMMEDIATE | NT_NOTIFY_LOCAL); + unsigned int assign_both = NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS; + if ((flags & assign_both) == assign_both) { + if ((listen_flags & assign_both) == 0) return false; + listen_flags &= ~assign_both; + flags &= ~assign_both; + } + if ((flags & ~listen_flags) != 0) return false; + + // must match local id or prefix + if (listener.entry != 0 && data.entry != listener.entry) return false; + if (listener.entry == 0 && + !llvm::StringRef(data.name).startswith(listener.prefix)) + return false; + + return true; +} + +unsigned int EntryNotifier::Add( + std::function callback, + StringRef prefix, unsigned int flags) { + if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true; + return DoAdd(callback, prefix, flags); +} + +unsigned int EntryNotifier::Add( + std::function callback, + unsigned int local_id, unsigned int flags) { + if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true; + return DoAdd(callback, Handle(m_inst, local_id, Handle::kEntry), flags); +} + +unsigned int EntryNotifier::AddPolled(unsigned int poller_uid, + llvm::StringRef prefix, + unsigned int flags) { + if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true; + return DoAdd(poller_uid, prefix, flags); +} + +unsigned int EntryNotifier::AddPolled(unsigned int poller_uid, + unsigned int local_id, + unsigned int flags) { + if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true; + return DoAdd(poller_uid, Handle(m_inst, local_id, Handle::kEntry), flags); +} + +void EntryNotifier::NotifyEntry(unsigned int local_id, StringRef name, + std::shared_ptr value, + unsigned int flags, + unsigned int only_listener) { + // optimization: don't generate needless local queue entries if we have + // no local listeners (as this is a common case on the server side) + if ((flags & NT_NOTIFY_LOCAL) != 0 && !m_local_notifiers) return; + DEBUG("notifying '" << name << "' (local=" << local_id + << "), flags=" << flags); + Send(only_listener, 0, Handle(m_inst, local_id, Handle::kEntry).handle(), + name, value, flags); +} diff --git a/src/main/native/cpp/EntryNotifier.h b/src/main/native/cpp/EntryNotifier.h new file mode 100644 index 0000000..c946811 --- /dev/null +++ b/src/main/native/cpp/EntryNotifier.h @@ -0,0 +1,108 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_ENTRYNOTIFIER_H_ +#define NT_ENTRYNOTIFIER_H_ + +#include "ntcore_cpp.h" + +#include + +#include "CallbackManager.h" +#include "Handle.h" +#include "IEntryNotifier.h" + +namespace wpi { +class Logger; +} + +namespace nt { + +namespace impl { + +struct EntryListenerData + : public ListenerData> { + EntryListenerData() = default; + EntryListenerData( + std::function callback_, + StringRef prefix_, unsigned int flags_) + : ListenerData(callback_), prefix(prefix_), flags(flags_) {} + EntryListenerData( + std::function callback_, + NT_Entry entry_, unsigned int flags_) + : ListenerData(callback_), entry(entry_), flags(flags_) {} + EntryListenerData(unsigned int poller_uid_, StringRef prefix_, + unsigned int flags_) + : ListenerData(poller_uid_), prefix(prefix_), flags(flags_) {} + EntryListenerData(unsigned int poller_uid_, NT_Entry entry_, + unsigned int flags_) + : ListenerData(poller_uid_), entry(entry_), flags(flags_) {} + + std::string prefix; + NT_Entry entry = 0; + unsigned int flags; +}; + +class EntryNotifierThread + : public CallbackThread { + public: + EntryNotifierThread(int inst) : m_inst(inst) {} + + bool Matches(const EntryListenerData& listener, + const EntryNotification& data); + + void SetListener(EntryNotification* data, unsigned int listener_uid) { + data->listener = + Handle(m_inst, listener_uid, Handle::kEntryListener).handle(); + } + + void DoCallback(std::function callback, + const EntryNotification& data) { + callback(data); + } + + int m_inst; +}; + +} // namespace impl + +class EntryNotifier + : public IEntryNotifier, + public CallbackManager { + friend class EntryNotifierTest; + friend class CallbackManager; + + public: + explicit EntryNotifier(int inst, wpi::Logger& logger); + + void Start(); + + bool local_notifiers() const override; + + unsigned int Add(std::function callback, + llvm::StringRef prefix, unsigned int flags); + unsigned int Add(std::function callback, + unsigned int local_id, unsigned int flags); + unsigned int AddPolled(unsigned int poller_uid, llvm::StringRef prefix, + unsigned int flags); + unsigned int AddPolled(unsigned int poller_uid, unsigned int local_id, + unsigned int flags); + + void NotifyEntry(unsigned int local_id, StringRef name, + std::shared_ptr value, unsigned int flags, + unsigned int only_listener = UINT_MAX) override; + + private: + int m_inst; + wpi::Logger& m_logger; + std::atomic_bool m_local_notifiers; +}; + +} // namespace nt + +#endif // NT_ENTRYNOTIFIER_H_ diff --git a/src/main/native/cpp/Handle.h b/src/main/native/cpp/Handle.h new file mode 100644 index 0000000..5383c0c --- /dev/null +++ b/src/main/native/cpp/Handle.h @@ -0,0 +1,65 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016-2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_HANDLE_H_ +#define NT_HANDLE_H_ + +#include "ntcore_c.h" + +namespace nt { + +// Handle data layout: +// Bits 30-28: Type +// Bits 27-20: Instance index +// Bits 19-0: Handle index (0/unused for instance handles) + +class Handle { + public: + enum Type { + kConnectionListener = 1, + kConnectionListenerPoller, + kEntry, + kEntryListener, + kEntryListenerPoller, + kInstance, + kLogger, + kLoggerPoller, + kRpcCall, + kRpcCallPoller + }; + enum { kIndexMax = 0xfffff }; + + Handle(NT_Handle handle) : m_handle(handle) {} + operator NT_Handle() const { return m_handle; } + + NT_Handle handle() const { return m_handle; } + + Handle(int inst, int index, Type type) { + if (inst < 0 || index < 0) { + m_handle = 0; + return; + } + m_handle = ((static_cast(type) & 0xf) << 27) | + ((inst & 0x7f) << 20) | (index & 0xfffff); + } + + int GetIndex() const { return static_cast(m_handle) & 0xfffff; } + Type GetType() const { + return static_cast((static_cast(m_handle) >> 27) & 0xf); + } + int GetInst() const { return (static_cast(m_handle) >> 20) & 0x7f; } + bool IsType(Type type) const { return type == GetType(); } + int GetTypedIndex(Type type) const { return IsType(type) ? GetIndex() : -1; } + int GetTypedInst(Type type) const { return IsType(type) ? GetInst() : -1; } + + private: + NT_Handle m_handle; +}; + +} // namespace nt + +#endif // NT_HANDLE_H_ diff --git a/src/main/native/cpp/IConnectionNotifier.h b/src/main/native/cpp/IConnectionNotifier.h new file mode 100644 index 0000000..d5f9705 --- /dev/null +++ b/src/main/native/cpp/IConnectionNotifier.h @@ -0,0 +1,29 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_ICONNECTIONNOTIFIER_H_ +#define NT_ICONNECTIONNOTIFIER_H_ + +#include + +#include "ntcore_cpp.h" + +namespace nt { + +class IConnectionNotifier { + public: + IConnectionNotifier() = default; + IConnectionNotifier(const IConnectionNotifier&) = delete; + IConnectionNotifier& operator=(const IConnectionNotifier&) = delete; + virtual ~IConnectionNotifier() = default; + virtual void NotifyConnection(bool connected, const ConnectionInfo& conn_info, + unsigned int only_listener = UINT_MAX) = 0; +}; + +} // namespace nt + +#endif // NT_ICONNECTIONNOTIFIER_H_ diff --git a/src/main/native/cpp/IDispatcher.h b/src/main/native/cpp/IDispatcher.h new file mode 100644 index 0000000..fc7d143 --- /dev/null +++ b/src/main/native/cpp/IDispatcher.h @@ -0,0 +1,34 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_IDISPATCHER_H_ +#define NT_IDISPATCHER_H_ + +#include + +#include "Message.h" + +namespace nt { + +class NetworkConnection; + +// Interface for generation of outgoing messages to break a dependency loop +// between Storage and Dispatcher. +class IDispatcher { + public: + IDispatcher() = default; + IDispatcher(const IDispatcher&) = delete; + IDispatcher& operator=(const IDispatcher&) = delete; + virtual ~IDispatcher() = default; + virtual void QueueOutgoing(std::shared_ptr msg, + NetworkConnection* only, + NetworkConnection* except) = 0; +}; + +} // namespace nt + +#endif // NT_IDISPATCHER_H_ diff --git a/src/main/native/cpp/IEntryNotifier.h b/src/main/native/cpp/IEntryNotifier.h new file mode 100644 index 0000000..4f6107c --- /dev/null +++ b/src/main/native/cpp/IEntryNotifier.h @@ -0,0 +1,31 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_IENTRYNOTIFIER_H_ +#define NT_IENTRYNOTIFIER_H_ + +#include + +#include "ntcore_cpp.h" + +namespace nt { + +class IEntryNotifier { + public: + IEntryNotifier() = default; + IEntryNotifier(const IEntryNotifier&) = delete; + IEntryNotifier& operator=(const IEntryNotifier&) = delete; + virtual ~IEntryNotifier() = default; + virtual bool local_notifiers() const = 0; + virtual void NotifyEntry(unsigned int local_id, StringRef name, + std::shared_ptr value, unsigned int flags, + unsigned int only_listener = UINT_MAX) = 0; +}; + +} // namespace nt + +#endif // NT_IENTRYNOTIFIER_H_ diff --git a/src/main/native/cpp/IRpcServer.h b/src/main/native/cpp/IRpcServer.h new file mode 100644 index 0000000..8a4f22e --- /dev/null +++ b/src/main/native/cpp/IRpcServer.h @@ -0,0 +1,38 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_IRPCSERVER_H_ +#define NT_IRPCSERVER_H_ + +#include + +#include "Message.h" +#include "ntcore_cpp.h" + +namespace nt { + +class IRpcServer { + public: + typedef std::function SendResponseFunc; + + IRpcServer() = default; + IRpcServer(const IRpcServer&) = delete; + IRpcServer& operator=(const IRpcServer&) = delete; + virtual ~IRpcServer() = default; + + virtual void RemoveRpc(unsigned int rpc_uid) = 0; + + virtual void ProcessRpc(unsigned int local_id, unsigned int call_uid, + StringRef name, StringRef params, + const ConnectionInfo& conn, + SendResponseFunc send_response, + unsigned int rpc_uid) = 0; +}; + +} // namespace nt + +#endif // NT_IRPCSERVER_H_ diff --git a/src/main/native/cpp/IStorage.h b/src/main/native/cpp/IStorage.h new file mode 100644 index 0000000..7ee9d05 --- /dev/null +++ b/src/main/native/cpp/IStorage.h @@ -0,0 +1,64 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_ISTORAGE_H_ +#define NT_ISTORAGE_H_ + +#include +#include +#include + +#include "llvm/ArrayRef.h" +#include "llvm/StringRef.h" + +#include "Message.h" +#include "ntcore_cpp.h" + +namespace nt { + +class IDispatcher; +class NetworkConnection; + +class IStorage { + public: + IStorage() = default; + IStorage(const IStorage&) = delete; + IStorage& operator=(const IStorage&) = delete; + virtual ~IStorage() = default; + + // Accessors required by Dispatcher. An interface is used for + // generation of outgoing messages to break a dependency loop between + // Storage and Dispatcher. + virtual void SetDispatcher(IDispatcher* dispatcher, bool server) = 0; + virtual void ClearDispatcher() = 0; + + // 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. + virtual NT_Type GetMessageEntryType(unsigned int id) const = 0; + + virtual void ProcessIncoming(std::shared_ptr msg, + NetworkConnection* conn, + std::weak_ptr conn_weak) = 0; + virtual void GetInitialAssignments( + NetworkConnection& conn, std::vector>* msgs) = 0; + virtual void ApplyInitialAssignments( + NetworkConnection& conn, llvm::ArrayRef> msgs, + bool new_server, std::vector>* out_msgs) = 0; + + // Filename-based save/load functions. Used both by periodic saves and + // accessible directly via the user API. + virtual const char* SavePersistent(StringRef filename, + bool periodic) const = 0; + virtual const char* LoadPersistent( + StringRef filename, + std::function warn) = 0; +}; + +} // namespace nt + +#endif // NT_ISTORAGE_H_ diff --git a/src/main/native/cpp/InstanceImpl.cpp b/src/main/native/cpp/InstanceImpl.cpp new file mode 100644 index 0000000..cee6cc1 --- /dev/null +++ b/src/main/native/cpp/InstanceImpl.cpp @@ -0,0 +1,106 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015-2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "InstanceImpl.h" + +using namespace nt; + +std::atomic InstanceImpl::s_default{-1}; +std::atomic InstanceImpl::s_fast_instances[10]; +wpi::UidVector, 10> InstanceImpl::s_instances; +std::mutex InstanceImpl::s_mutex; + +using namespace std::placeholders; + +InstanceImpl::InstanceImpl(int inst) + : logger_impl(inst), + logger(std::bind(&LoggerImpl::Log, &logger_impl, _1, _2, _3, _4)), + connection_notifier(inst), + entry_notifier(inst, logger), + rpc_server(inst, logger), + storage(entry_notifier, rpc_server, logger), + dispatcher(storage, connection_notifier, logger), + ds_client(dispatcher, logger) {} + +InstanceImpl::~InstanceImpl() { logger.SetLogger(nullptr); } + +InstanceImpl* InstanceImpl::GetDefault() { return Get(GetDefaultIndex()); } + +InstanceImpl* InstanceImpl::Get(int inst) { + if (inst < 0) return nullptr; + + // fast path, just an atomic read + if (static_cast(inst) < + (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) { + InstanceImpl* ptr = s_fast_instances[inst]; + if (ptr) return ptr; + } + + // slow path + std::lock_guard lock(s_mutex); + + // static fast-path block + if (static_cast(inst) < + (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) { + // double-check + return s_fast_instances[inst]; + } + + // vector + if (static_cast(inst) < s_instances.size()) { + return s_instances[inst].get(); + } + + // doesn't exist + return nullptr; +} + +int InstanceImpl::GetDefaultIndex() { + int inst = s_default; + if (inst >= 0) return inst; + + // slow path + std::lock_guard lock(s_mutex); + + // double-check + inst = s_default; + if (inst >= 0) return inst; + + // alloc and save + inst = AllocImpl(); + s_default = inst; + return inst; +} + +int InstanceImpl::Alloc() { + std::lock_guard lock(s_mutex); + return AllocImpl(); +} + +int InstanceImpl::AllocImpl() { + unsigned int inst = s_instances.emplace_back(); + InstanceImpl* ptr = new InstanceImpl(inst); + s_instances[inst].reset(ptr); + + if (inst < (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) { + s_fast_instances[inst] = ptr; + } + + return static_cast(inst); +} + +void InstanceImpl::Destroy(int inst) { + std::lock_guard lock(s_mutex); + if (inst < 0 || static_cast(inst) >= s_instances.size()) return; + + if (static_cast(inst) < + (sizeof(s_fast_instances) / sizeof(s_fast_instances[0]))) { + s_fast_instances[inst] = nullptr; + } + + s_instances.erase(inst); +} diff --git a/src/main/native/cpp/InstanceImpl.h b/src/main/native/cpp/InstanceImpl.h new file mode 100644 index 0000000..ab89080 --- /dev/null +++ b/src/main/native/cpp/InstanceImpl.h @@ -0,0 +1,60 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2016-2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_INSTANCEIMPL_H_ +#define NT_INSTANCEIMPL_H_ + +#include +#include +#include + +#include "support/UidVector.h" + +#include "ConnectionNotifier.h" +#include "Dispatcher.h" +#include "DsClient.h" +#include "EntryNotifier.h" +#include "Log.h" +#include "LoggerImpl.h" +#include "RpcServer.h" +#include "Storage.h" + +namespace nt { + +class InstanceImpl { + public: + explicit InstanceImpl(int inst); + ~InstanceImpl(); + + // Instance repository + static InstanceImpl* GetDefault(); + static InstanceImpl* Get(int inst); + static int GetDefaultIndex(); + static int Alloc(); + static void Destroy(int inst); + + LoggerImpl logger_impl; + wpi::Logger logger; + ConnectionNotifier connection_notifier; + EntryNotifier entry_notifier; + RpcServer rpc_server; + Storage storage; + Dispatcher dispatcher; + DsClient ds_client; + + private: + static int AllocImpl(); + + static std::atomic s_default; + static std::atomic s_fast_instances[10]; + static wpi::UidVector, 10> s_instances; + static std::mutex s_mutex; +}; + +} // namespace nt + +#endif // NT_INSTANCEIMPL_H_ diff --git a/src/main/native/cpp/Log.cpp b/src/main/native/cpp/Log.cpp deleted file mode 100644 index 4ba1915..0000000 --- a/src/main/native/cpp/Log.cpp +++ /dev/null @@ -1,40 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* Copyright (c) FIRST 2015. All Rights Reserved. */ -/* Open Source Software - may be modified and shared by FRC teams. The code */ -/* must be accompanied by the FIRST BSD license file in the root directory of */ -/* the project. */ -/*----------------------------------------------------------------------------*/ - -#include "Log.h" - -#include "llvm/Path.h" -#include "llvm/StringRef.h" -#include "llvm/raw_ostream.h" - -using namespace nt; - -ATOMIC_STATIC_INIT(Logger) - -static void def_log_func(unsigned int level, const char* file, - unsigned int line, const char* msg) { - if (level == 20) { - llvm::errs() << "NT: " << msg << '\n'; - return; - } - - llvm::StringRef levelmsg; - if (level >= 50) - levelmsg = "CRITICAL: "; - else if (level >= 40) - levelmsg = "ERROR: "; - else if (level >= 30) - levelmsg = "WARNING: "; - else - return; - llvm::errs() << "NT: " << levelmsg << msg << " (" - << llvm::sys::path::filename(file) << ':' << line << ")\n"; -} - -Logger::Logger() { SetLogger(def_log_func); } - -Logger::~Logger() {} diff --git a/src/main/native/cpp/Log.h b/src/main/native/cpp/Log.h index d56ba04..221e8cc 100644 --- a/src/main/native/cpp/Log.h +++ b/src/main/native/cpp/Log.h @@ -8,38 +8,19 @@ #ifndef NT_LOG_H_ #define NT_LOG_H_ -#include "support/atomic_static.h" #include "support/Logger.h" -namespace nt { - -class Logger : public wpi::Logger { - public: - static Logger& GetInstance() { - ATOMIC_STATIC(Logger, instance); - return instance; - } - ~Logger(); - - private: - Logger(); - - ATOMIC_STATIC_DECL(Logger) -}; - -#define LOG(level, x) WPI_LOG(nt::Logger::GetInstance(), level, x) +#define LOG(level, x) WPI_LOG(m_logger, level, x) #undef ERROR -#define ERROR(x) WPI_ERROR(nt::Logger::GetInstance(), x) -#define WARNING(x) WPI_WARNING(nt::Logger::GetInstance(), x) -#define INFO(x) WPI_INFO(nt::Logger::GetInstance(), x) - -#define DEBUG(x) WPI_DEBUG(nt::Logger::GetInstance(), x) -#define DEBUG1(x) WPI_DEBUG1(nt::Logger::GetInstance(), x) -#define DEBUG2(x) WPI_DEBUG2(nt::Logger::GetInstance(), x) -#define DEBUG3(x) WPI_DEBUG3(nt::Logger::GetInstance(), x) -#define DEBUG4(x) WPI_DEBUG4(nt::Logger::GetInstance(), x) - -} // namespace nt +#define ERROR(x) WPI_ERROR(m_logger, x) +#define WARNING(x) WPI_WARNING(m_logger, x) +#define INFO(x) WPI_INFO(m_logger, x) + +#define DEBUG(x) WPI_DEBUG(m_logger, x) +#define DEBUG1(x) WPI_DEBUG1(m_logger, x) +#define DEBUG2(x) WPI_DEBUG2(m_logger, x) +#define DEBUG3(x) WPI_DEBUG3(m_logger, x) +#define DEBUG4(x) WPI_DEBUG4(m_logger, x) #endif // NT_LOG_H_ diff --git a/src/main/native/cpp/LoggerImpl.cpp b/src/main/native/cpp/LoggerImpl.cpp new file mode 100644 index 0000000..0019d50 --- /dev/null +++ b/src/main/native/cpp/LoggerImpl.cpp @@ -0,0 +1,77 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "LoggerImpl.h" + +#include "llvm/Path.h" +#include "llvm/SmallString.h" +#include "llvm/StringRef.h" +#include "llvm/raw_ostream.h" + +using namespace nt; + +static void DefaultLogger(unsigned int level, const char* file, + unsigned int line, const char* msg) { + llvm::SmallString<128> buf; + llvm::raw_svector_ostream oss(buf); + if (level == 20) { + oss << "NT: " << msg << '\n'; + llvm::errs() << oss.str(); + return; + } + + llvm::StringRef levelmsg; + if (level >= 50) + levelmsg = "CRITICAL: "; + else if (level >= 40) + levelmsg = "ERROR: "; + else if (level >= 30) + levelmsg = "WARNING: "; + else + return; + oss << "NT: " << levelmsg << msg << " (" << file << ':' << line << ")\n"; + llvm::errs() << oss.str(); +} + +LoggerImpl::LoggerImpl(int inst) : m_inst(inst) {} + +void LoggerImpl::Start() { DoStart(m_inst); } + +unsigned int LoggerImpl::Add( + std::function callback, unsigned int min_level, + unsigned int max_level) { + return DoAdd(callback, min_level, max_level); +} + +unsigned int LoggerImpl::AddPolled(unsigned int poller_uid, + unsigned int min_level, + unsigned int max_level) { + return DoAdd(poller_uid, min_level, max_level); +} + +unsigned int LoggerImpl::GetMinLevel() { + auto thr = GetThread(); + if (!thr) return NT_LOG_INFO; + unsigned int level = NT_LOG_INFO; + for (size_t i = 0; i < thr->m_listeners.size(); ++i) { + const auto& listener = thr->m_listeners[i]; + if (listener && listener.min_level < level) level = listener.min_level; + } + return level; +} + +void LoggerImpl::Log(unsigned int level, const char* file, unsigned int line, + const char* msg) { + // this is safe because it's null terminated and always the end + const char* filename = llvm::sys::path::filename(file).data(); + { + auto thr = GetThread(); + if (!thr || thr->m_listeners.empty()) + DefaultLogger(level, filename, line, msg); + } + Send(UINT_MAX, 0, level, filename, line, msg); +} diff --git a/src/main/native/cpp/LoggerImpl.h b/src/main/native/cpp/LoggerImpl.h new file mode 100644 index 0000000..ccfe4ce --- /dev/null +++ b/src/main/native/cpp/LoggerImpl.h @@ -0,0 +1,84 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_LOGGERIMPL_H_ +#define NT_LOGGERIMPL_H_ + +#include "ntcore_cpp.h" + +#include "CallbackManager.h" +#include "Handle.h" + +namespace nt { + +namespace impl { + +struct LoggerListenerData + : public ListenerData> { + LoggerListenerData() = default; + LoggerListenerData(std::function callback_, + unsigned int min_level_, unsigned int max_level_) + : ListenerData(callback_), min_level(min_level_), max_level(max_level_) {} + LoggerListenerData(unsigned int poller_uid_, unsigned int min_level_, + unsigned int max_level_) + : ListenerData(poller_uid_), + min_level(min_level_), + max_level(max_level_) {} + + unsigned int min_level; + unsigned int max_level; +}; + +class LoggerThread + : public CallbackThread { + public: + LoggerThread(int inst) : m_inst(inst) {} + + bool Matches(const LoggerListenerData& listener, const LogMessage& data) { + return data.level >= listener.min_level && data.level <= listener.max_level; + } + + void SetListener(LogMessage* data, unsigned int listener_uid) { + data->logger = Handle(m_inst, listener_uid, Handle::kLogger).handle(); + } + + void DoCallback(std::function callback, + const LogMessage& data) { + callback(data); + } + + int m_inst; +}; + +} // namespace impl + +class LoggerImpl : public CallbackManager { + friend class LoggerTest; + friend class CallbackManager; + + public: + explicit LoggerImpl(int inst); + + void Start(); + + unsigned int Add(std::function callback, + unsigned int min_level, unsigned int max_level); + unsigned int AddPolled(unsigned int poller_uid, unsigned int min_level, + unsigned int max_level); + + unsigned int GetMinLevel(); + + void Log(unsigned int level, const char* file, unsigned int line, + const char* msg); + + private: + int m_inst; +}; + +} // namespace nt + +#endif // NT_CONNECTIONNOTIFIER_H_ diff --git a/src/main/native/cpp/Message.cpp b/src/main/native/cpp/Message.cpp index 9dab685..ebdfda8 100644 --- a/src/main/native/cpp/Message.cpp +++ b/src/main/native/cpp/Message.cpp @@ -77,7 +77,7 @@ std::shared_ptr Message::Read(WireDecoder& decoder, } else { type = get_entry_type(msg->m_id); } - DEBUG4("update message data type: " << type); + WPI_DEBUG4(decoder.logger(), "update message data type: " << type); msg->m_value = decoder.ReadValue(type); if (!msg->m_value) return nullptr; break; @@ -143,7 +143,7 @@ std::shared_ptr Message::Read(WireDecoder& decoder, } default: decoder.set_error("unrecognized message type"); - INFO("unrecognized message type: " << msg_type); + WPI_INFO(decoder.logger(), "unrecognized message type: " << msg_type); return nullptr; } return msg; @@ -211,9 +211,9 @@ std::shared_ptr Message::ExecuteRpc(unsigned int id, unsigned int uid, } std::shared_ptr Message::RpcResponse(unsigned int id, unsigned int uid, - llvm::StringRef results) { + llvm::StringRef result) { auto msg = std::make_shared(kRpcResponse, private_init()); - msg->m_str = results; + msg->m_str = result; msg->m_id = id; msg->m_seq_num_uid = uid; return msg; diff --git a/src/main/native/cpp/Message.h b/src/main/native/cpp/Message.h index 3047834..46baebd 100644 --- a/src/main/native/cpp/Message.h +++ b/src/main/native/cpp/Message.h @@ -12,7 +12,7 @@ #include #include -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" namespace nt { @@ -96,7 +96,7 @@ class Message { static std::shared_ptr ExecuteRpc(unsigned int id, unsigned int uid, llvm::StringRef params); static std::shared_ptr RpcResponse(unsigned int id, unsigned int uid, - llvm::StringRef results); + llvm::StringRef result); Message(const Message&) = delete; Message& operator=(const Message&) = delete; diff --git a/src/main/native/cpp/NetworkConnection.cpp b/src/main/native/cpp/NetworkConnection.cpp index a0b06c6..dbffc38 100644 --- a/src/main/native/cpp/NetworkConnection.cpp +++ b/src/main/native/cpp/NetworkConnection.cpp @@ -10,22 +10,24 @@ #include "support/raw_socket_istream.h" #include "support/timestamp.h" #include "tcpsockets/NetworkStream.h" + +#include "IConnectionNotifier.h" #include "Log.h" -#include "Notifier.h" #include "WireDecoder.h" #include "WireEncoder.h" using namespace nt; -std::atomic_uint NetworkConnection::s_uid; - -NetworkConnection::NetworkConnection(std::unique_ptr stream, - Notifier& notifier, +NetworkConnection::NetworkConnection(unsigned int uid, + std::unique_ptr stream, + IConnectionNotifier& notifier, + wpi::Logger& logger, HandshakeFunc handshake, Message::GetEntryTypeFunc get_entry_type) - : m_uid(s_uid.fetch_add(1)), + : m_uid(uid), m_stream(std::move(stream)), m_notifier(notifier), + m_logger(logger), m_handshake(handshake), m_get_entry_type(get_entry_type), m_state(kCreated) { @@ -112,12 +114,6 @@ void NetworkConnection::set_state(State state) { m_state = state; } -void NetworkConnection::NotifyIfActive( - ConnectionListenerCallback callback) const { - std::lock_guard lock(m_state_mutex); - if (m_state == kActive) m_notifier.NotifyConnection(true, info(), callback); -} - std::string NetworkConnection::remote_id() const { std::lock_guard lock(m_remote_id_mutex); return m_remote_id; @@ -130,7 +126,7 @@ void NetworkConnection::set_remote_id(StringRef remote_id) { void NetworkConnection::ReadThreadMain() { wpi::raw_socket_istream is(*m_stream); - WireDecoder decoder(is, m_proto_rev); + WireDecoder decoder(is, m_proto_rev, m_logger); set_state(kHandshake); if (!m_handshake(*this, diff --git a/src/main/native/cpp/NetworkConnection.h b/src/main/native/cpp/NetworkConnection.h index 6f79fa3..01edafd 100644 --- a/src/main/native/cpp/NetworkConnection.h +++ b/src/main/native/cpp/NetworkConnection.h @@ -18,12 +18,13 @@ #include "ntcore_cpp.h" namespace wpi { +class Logger; class NetworkStream; } namespace nt { -class Notifier; +class IConnectionNotifier; class NetworkConnection { public: @@ -40,8 +41,10 @@ class NetworkConnection { typedef std::vector> Outgoing; typedef wpi::ConcurrentQueue OutgoingQueue; - NetworkConnection(std::unique_ptr stream, - Notifier& notifier, HandshakeFunc handshake, + NetworkConnection(unsigned int uid, + std::unique_ptr stream, + IConnectionNotifier& notifier, wpi::Logger& logger, + HandshakeFunc handshake, Message::GetEntryTypeFunc get_entry_type); ~NetworkConnection(); @@ -60,7 +63,6 @@ class NetworkConnection { void QueueOutgoing(std::shared_ptr msg); void PostOutgoing(bool keep_alive); - void NotifyIfActive(ConnectionListenerCallback callback) const; unsigned int uid() const { return m_uid; } @@ -82,11 +84,10 @@ class NetworkConnection { void ReadThreadMain(); void WriteThreadMain(); - static std::atomic_uint s_uid; - unsigned int m_uid; std::unique_ptr m_stream; - Notifier& m_notifier; + IConnectionNotifier& m_notifier; + wpi::Logger& m_logger; OutgoingQueue m_outgoing; HandshakeFunc m_handshake; Message::GetEntryTypeFunc m_get_entry_type; diff --git a/src/main/native/cpp/Notifier.cpp b/src/main/native/cpp/Notifier.cpp deleted file mode 100644 index b19d979..0000000 --- a/src/main/native/cpp/Notifier.cpp +++ /dev/null @@ -1,258 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* Copyright (c) FIRST 2015. All Rights Reserved. */ -/* Open Source Software - may be modified and shared by FRC teams. The code */ -/* must be accompanied by the FIRST BSD license file in the root directory of */ -/* the project. */ -/*----------------------------------------------------------------------------*/ - -#include "Notifier.h" - -#include -#include - -using namespace nt; - -ATOMIC_STATIC_INIT(Notifier) -bool Notifier::s_destroyed = false; - -namespace { -// Vector which provides an integrated freelist for removal and reuse of -// individual elements. -template -class UidVector { - public: - typedef typename std::vector::size_type size_type; - - size_type size() const { return m_vector.size(); } - T& operator[](size_type i) { return m_vector[i]; } - const T& operator[](size_type i) const { return m_vector[i]; } - - // Add a new T to the vector. If there are elements on the freelist, - // reuses the last one; otherwise adds to the end of the vector. - // Returns the resulting element index (+1). - template - unsigned int emplace_back(Args&&... args) { - unsigned int uid; - if (m_free.empty()) { - uid = m_vector.size(); - m_vector.emplace_back(std::forward(args)...); - } else { - uid = m_free.back(); - m_free.pop_back(); - m_vector[uid] = T(std::forward(args)...); - } - return uid + 1; - } - - // Removes the identified element by replacing it with a default-constructed - // one. The element is added to the freelist for later reuse. - void erase(unsigned int uid) { - --uid; - if (uid >= m_vector.size() || !m_vector[uid]) return; - m_free.push_back(uid); - m_vector[uid] = T(); - } - - private: - std::vector m_vector; - std::vector m_free; -}; - -} // anonymous namespace - -class Notifier::Thread : public wpi::SafeThread { - public: - Thread(std::function on_start, std::function on_exit) - : m_on_start(on_start), m_on_exit(on_exit) {} - - void Main(); - - struct EntryListener { - EntryListener() = default; - EntryListener(StringRef prefix_, EntryListenerCallback callback_, - unsigned int flags_) - : prefix(prefix_), callback(callback_), flags(flags_) {} - - explicit operator bool() const { return bool(callback); } - - std::string prefix; - EntryListenerCallback callback; - unsigned int flags; - }; - UidVector m_entry_listeners; - UidVector m_conn_listeners; - - struct EntryNotification { - EntryNotification(llvm::StringRef name_, std::shared_ptr value_, - unsigned int flags_, EntryListenerCallback only_) - : name(name_), value(value_), flags(flags_), only(only_) {} - - std::string name; - std::shared_ptr value; - unsigned int flags; - EntryListenerCallback only; - }; - std::queue m_entry_notifications; - - struct ConnectionNotification { - ConnectionNotification(bool connected_, const ConnectionInfo& conn_info_, - ConnectionListenerCallback only_) - : connected(connected_), conn_info(conn_info_), only(only_) {} - - bool connected; - ConnectionInfo conn_info; - ConnectionListenerCallback only; - }; - std::queue m_conn_notifications; - - std::function m_on_start; - std::function m_on_exit; -}; - -Notifier::Notifier() { - m_local_notifiers = false; - s_destroyed = false; -} - -Notifier::~Notifier() { s_destroyed = true; } - -void Notifier::Start() { - auto thr = m_owner.GetThread(); - if (!thr) m_owner.Start(new Thread(m_on_start, m_on_exit)); -} - -void Notifier::Stop() { m_owner.Stop(); } - -void Notifier::Thread::Main() { - if (m_on_start) m_on_start(); - - std::unique_lock lock(m_mutex); - while (m_active) { - while (m_entry_notifications.empty() && m_conn_notifications.empty()) { - m_cond.wait(lock); - if (!m_active) goto done; - } - - // Entry notifications - while (!m_entry_notifications.empty()) { - if (!m_active) goto done; - auto item = std::move(m_entry_notifications.front()); - m_entry_notifications.pop(); - - if (!item.value) continue; - StringRef name(item.name); - - if (item.only) { - // Don't hold mutex during callback execution! - lock.unlock(); - item.only(0, name, item.value, item.flags); - lock.lock(); - continue; - } - - // Use index because iterator might get invalidated. - for (std::size_t i = 0; i < m_entry_listeners.size(); ++i) { - if (!m_entry_listeners[i]) continue; // removed - - // Flags must be within requested flag set for this listener. - // Because assign messages can result in both a value and flags update, - // we handle that case specially. - unsigned int listen_flags = m_entry_listeners[i].flags; - unsigned int flags = item.flags; - unsigned int assign_both = NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS; - if ((flags & assign_both) == assign_both) { - if ((listen_flags & assign_both) == 0) continue; - listen_flags &= ~assign_both; - flags &= ~assign_both; - } - if ((flags & ~listen_flags) != 0) continue; - - // must match prefix - if (!name.startswith(m_entry_listeners[i].prefix)) continue; - - // make a copy of the callback so we can safely release the mutex - auto callback = m_entry_listeners[i].callback; - - // Don't hold mutex during callback execution! - lock.unlock(); - callback(i + 1, name, item.value, item.flags); - lock.lock(); - } - } - - // Connection notifications - while (!m_conn_notifications.empty()) { - if (!m_active) goto done; - auto item = std::move(m_conn_notifications.front()); - m_conn_notifications.pop(); - - if (item.only) { - // Don't hold mutex during callback execution! - lock.unlock(); - item.only(0, item.connected, item.conn_info); - lock.lock(); - continue; - } - - // Use index because iterator might get invalidated. - for (std::size_t i = 0; i < m_conn_listeners.size(); ++i) { - if (!m_conn_listeners[i]) continue; // removed - auto callback = m_conn_listeners[i]; - // Don't hold mutex during callback execution! - lock.unlock(); - callback(i + 1, item.connected, item.conn_info); - lock.lock(); - } - } - } - -done: - if (m_on_exit) m_on_exit(); -} - -unsigned int Notifier::AddEntryListener(StringRef prefix, - EntryListenerCallback callback, - unsigned int flags) { - Start(); - auto thr = m_owner.GetThread(); - if ((flags & NT_NOTIFY_LOCAL) != 0) m_local_notifiers = true; - return thr->m_entry_listeners.emplace_back(prefix, callback, flags); -} - -void Notifier::RemoveEntryListener(unsigned int entry_listener_uid) { - auto thr = m_owner.GetThread(); - if (!thr) return; - thr->m_entry_listeners.erase(entry_listener_uid); -} - -void Notifier::NotifyEntry(StringRef name, std::shared_ptr value, - unsigned int flags, EntryListenerCallback only) { - // optimization: don't generate needless local queue entries if we have - // no local listeners (as this is a common case on the server side) - if ((flags & NT_NOTIFY_LOCAL) != 0 && !m_local_notifiers) return; - auto thr = m_owner.GetThread(); - if (!thr) return; - thr->m_entry_notifications.emplace(name, value, flags, only); - thr->m_cond.notify_one(); -} - -unsigned int Notifier::AddConnectionListener( - ConnectionListenerCallback callback) { - Start(); - auto thr = m_owner.GetThread(); - return thr->m_conn_listeners.emplace_back(callback); -} - -void Notifier::RemoveConnectionListener(unsigned int conn_listener_uid) { - auto thr = m_owner.GetThread(); - if (!thr) return; - thr->m_conn_listeners.erase(conn_listener_uid); -} - -void Notifier::NotifyConnection(bool connected, const ConnectionInfo& conn_info, - ConnectionListenerCallback only) { - auto thr = m_owner.GetThread(); - if (!thr) return; - thr->m_conn_notifications.emplace(connected, conn_info, only); - thr->m_cond.notify_one(); -} diff --git a/src/main/native/cpp/Notifier.h b/src/main/native/cpp/Notifier.h deleted file mode 100644 index 49c9ec9..0000000 --- a/src/main/native/cpp/Notifier.h +++ /dev/null @@ -1,69 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* Copyright (c) FIRST 2015. All Rights Reserved. */ -/* Open Source Software - may be modified and shared by FRC teams. The code */ -/* must be accompanied by the FIRST BSD license file in the root directory of */ -/* the project. */ -/*----------------------------------------------------------------------------*/ - -#ifndef NT_NOTIFIER_H_ -#define NT_NOTIFIER_H_ - -#include - -#include "support/atomic_static.h" -#include "support/SafeThread.h" -#include "ntcore_cpp.h" - -namespace nt { - -class Notifier { - friend class NotifierTest; - - public: - static Notifier& GetInstance() { - ATOMIC_STATIC(Notifier, instance); - return instance; - } - ~Notifier(); - - void Start(); - void Stop(); - - bool local_notifiers() const { return m_local_notifiers; } - static bool destroyed() { return s_destroyed; } - - void SetOnStart(std::function on_start) { m_on_start = on_start; } - void SetOnExit(std::function on_exit) { m_on_exit = on_exit; } - - unsigned int AddEntryListener(llvm::StringRef prefix, - EntryListenerCallback callback, - unsigned int flags); - void RemoveEntryListener(unsigned int entry_listener_uid); - - void NotifyEntry(StringRef name, std::shared_ptr value, - unsigned int flags, EntryListenerCallback only = nullptr); - - unsigned int AddConnectionListener(ConnectionListenerCallback callback); - void RemoveConnectionListener(unsigned int conn_listener_uid); - - void NotifyConnection(bool connected, const ConnectionInfo& conn_info, - ConnectionListenerCallback only = nullptr); - - private: - Notifier(); - - class Thread; - wpi::SafeThreadOwner m_owner; - - std::atomic_bool m_local_notifiers; - - std::function m_on_start; - std::function m_on_exit; - - ATOMIC_STATIC_DECL(Notifier) - static bool s_destroyed; -}; - -} // namespace nt - -#endif // NT_NOTIFIER_H_ diff --git a/src/main/native/cpp/RpcServer.cpp b/src/main/native/cpp/RpcServer.cpp index df3cb4f..b6fa712 100644 --- a/src/main/native/cpp/RpcServer.cpp +++ b/src/main/native/cpp/RpcServer.cpp @@ -7,145 +7,42 @@ #include "RpcServer.h" -#include - -#include "Log.h" - using namespace nt; -ATOMIC_STATIC_INIT(RpcServer) - -class RpcServer::Thread : public wpi::SafeThread { - public: - Thread(std::function on_start, std::function on_exit) - : m_on_start(on_start), m_on_exit(on_exit) {} - - void Main(); - - std::queue m_call_queue; - - std::function m_on_start; - std::function m_on_exit; -}; - -RpcServer::RpcServer() { m_terminating = false; } - -RpcServer::~RpcServer() { - Logger::GetInstance().SetLogger(nullptr); - m_terminating = true; - m_poll_cond.notify_all(); -} - -void RpcServer::Start() { - auto thr = m_owner.GetThread(); - if (!thr) m_owner.Start(new Thread(m_on_start, m_on_exit)); -} +RpcServer::RpcServer(int inst, wpi::Logger& logger) + : m_inst(inst), m_logger(logger) {} -void RpcServer::Stop() { m_owner.Stop(); } +void RpcServer::Start() { DoStart(m_inst, m_logger); } -void RpcServer::ProcessRpc(StringRef name, std::shared_ptr msg, - RpcCallback func, unsigned int conn_id, - SendMsgFunc send_response, - const ConnectionInfo& conn_info) { - if (func) { - auto thr = m_owner.GetThread(); - if (!thr) return; - thr->m_call_queue.emplace(name, msg, func, conn_id, send_response, - conn_info); - thr->m_cond.notify_one(); - } else { - std::lock_guard lock(m_mutex); - m_poll_queue.emplace(name, msg, func, conn_id, send_response, conn_info); - m_poll_cond.notify_one(); - } +unsigned int RpcServer::Add( + std::function callback) { + return DoAdd(callback); } -bool RpcServer::PollRpc(bool blocking, RpcCallInfo* call_info) { - return PollRpc(blocking, kTimeout_Indefinite, call_info); +unsigned int RpcServer::AddPolled(unsigned int poller_uid) { + return DoAdd(poller_uid); } -bool RpcServer::PollRpc(bool blocking, double time_out, - RpcCallInfo* call_info) { - std::unique_lock lock(m_mutex); -#if defined(_MSC_VER) && _MSC_VER < 1900 - auto timeout_time = std::chrono::steady_clock::now() + - std::chrono::duration( - static_cast(time_out * 1e9)); -#else - auto timeout_time = std::chrono::steady_clock::now() + - std::chrono::duration(time_out); -#endif - while (m_poll_queue.empty()) { - if (!blocking || m_terminating) return false; - if (time_out < 0) { - m_poll_cond.wait(lock); - } else { - auto timed_out = m_poll_cond.wait_until(lock, timeout_time); - if (timed_out == std::cv_status::timeout) { - return false; - } - } - if (m_terminating) return false; - } +void RpcServer::RemoveRpc(unsigned int rpc_uid) { Remove(rpc_uid); } - auto& item = m_poll_queue.front(); - unsigned int call_uid; - // do not include conn id if the result came from the server - if (item.conn_id != 0xffff) - call_uid = (item.conn_id << 16) | item.msg->seq_num_uid(); - else - call_uid = item.msg->seq_num_uid(); - call_info->rpc_id = item.msg->id(); - call_info->call_uid = call_uid; - call_info->name = std::move(item.name); - call_info->params = item.msg->str(); - m_response_map.insert(std::make_pair(std::make_pair(item.msg->id(), call_uid), - item.send_response)); - m_poll_queue.pop(); - return true; +void RpcServer::ProcessRpc(unsigned int local_id, unsigned int call_uid, + StringRef name, StringRef params, + const ConnectionInfo& conn, + SendResponseFunc send_response, + unsigned int rpc_uid) { + Send(rpc_uid, Handle(m_inst, local_id, Handle::kEntry).handle(), + Handle(m_inst, call_uid, Handle::kRpcCall).handle(), name, params, conn, + send_response); } -void RpcServer::PostRpcResponse(unsigned int rpc_id, unsigned int call_uid, +void RpcServer::PostRpcResponse(unsigned int local_id, unsigned int call_uid, llvm::StringRef result) { - auto i = m_response_map.find(std::make_pair(rpc_id, call_uid)); - if (i == m_response_map.end()) { + auto thr = GetThread(); + auto i = thr->m_response_map.find(impl::RpcIdPair{local_id, call_uid}); + if (i == thr->m_response_map.end()) { WARNING("posting RPC response to nonexistent call (or duplicate response)"); return; } - (i->getSecond())(Message::RpcResponse(rpc_id, call_uid, result)); - m_response_map.erase(i); -} - -void RpcServer::Thread::Main() { - if (m_on_start) m_on_start(); - - std::unique_lock lock(m_mutex); - std::string tmp; - while (m_active) { - while (m_call_queue.empty()) { - m_cond.wait(lock); - if (!m_active) goto done; - } - - while (!m_call_queue.empty()) { - if (!m_active) goto done; - auto item = std::move(m_call_queue.front()); - m_call_queue.pop(); - - DEBUG4("rpc calling " << item.name); - - if (item.name.empty() || !item.msg || !item.func || !item.send_response) - continue; - - // Don't hold mutex during callback execution! - lock.unlock(); - auto result = item.func(item.name, item.msg->str(), item.conn_info); - item.send_response(Message::RpcResponse(item.msg->id(), - item.msg->seq_num_uid(), result)); - lock.lock(); - } - } - -done: - if (m_on_exit) m_on_exit(); + (i->getSecond())(result); + thr->m_response_map.erase(i); } diff --git a/src/main/native/cpp/RpcServer.h b/src/main/native/cpp/RpcServer.h index 326ad18..a79e1e2 100644 --- a/src/main/native/cpp/RpcServer.h +++ b/src/main/native/cpp/RpcServer.h @@ -8,86 +8,100 @@ #ifndef NT_RPCSERVER_H_ #define NT_RPCSERVER_H_ -#include -#include -#include -#include -#include - #include "llvm/DenseMap.h" -#include "support/atomic_static.h" -#include "support/SafeThread.h" -#include "Message.h" -#include "ntcore_cpp.h" + +#include "CallbackManager.h" +#include "Handle.h" +#include "IRpcServer.h" +#include "Log.h" namespace nt { -class RpcServer { - friend class RpcServerTest; +namespace impl { - public: - static RpcServer& GetInstance() { - ATOMIC_STATIC(RpcServer, instance); - return instance; - } - ~RpcServer(); +typedef std::pair RpcIdPair; - typedef std::function)> SendMsgFunc; +struct RpcNotifierData : public RpcAnswer { + RpcNotifierData(NT_Entry entry_, NT_RpcCall call_, StringRef name_, + StringRef params_, const ConnectionInfo& conn_, + IRpcServer::SendResponseFunc send_response_) + : RpcAnswer{entry_, call_, name_, params_, conn_}, + send_response{send_response_} {} - void Start(); - void Stop(); + IRpcServer::SendResponseFunc send_response; +}; - void SetOnStart(std::function on_start) { m_on_start = on_start; } - void SetOnExit(std::function on_exit) { m_on_exit = on_exit; } +using RpcListenerData = + ListenerData>; - void ProcessRpc(StringRef name, std::shared_ptr msg, - RpcCallback func, unsigned int conn_id, - SendMsgFunc send_response, const ConnectionInfo& conn_info); +class RpcServerThread + : public CallbackThread { + public: + RpcServerThread(int inst, wpi::Logger& logger) + : m_inst(inst), m_logger(logger) {} - bool PollRpc(bool blocking, RpcCallInfo* call_info); - bool PollRpc(bool blocking, double time_out, RpcCallInfo* call_info); - void PostRpcResponse(unsigned int rpc_id, unsigned int call_uid, - llvm::StringRef result); + bool Matches(const RpcListenerData& listener, const RpcNotifierData& data) { + return !data.name.empty() && data.send_response; + } - private: - RpcServer(); + void SetListener(RpcNotifierData* data, unsigned int listener_uid) { + unsigned int local_id = Handle{data->entry}.GetIndex(); + unsigned int call_uid = Handle{data->call}.GetIndex(); + RpcIdPair lookup_uid{local_id, call_uid}; + m_response_map.insert(std::make_pair(lookup_uid, data->send_response)); + } - class Thread; - wpi::SafeThreadOwner m_owner; + void DoCallback(std::function callback, + const RpcNotifierData& data) { + DEBUG4("rpc calling " << data.name); + unsigned int local_id = Handle{data.entry}.GetIndex(); + unsigned int call_uid = Handle{data.call}.GetIndex(); + RpcIdPair lookup_uid{local_id, call_uid}; + callback(data); + { + std::lock_guard lock(m_mutex); + auto i = m_response_map.find(lookup_uid); + if (i != m_response_map.end()) { + // post an empty response and erase it + (i->getSecond())(""); + m_response_map.erase(i); + } + } + } + + int m_inst; + wpi::Logger& m_logger; + llvm::DenseMap m_response_map; +}; - struct RpcCall { - RpcCall(StringRef name_, std::shared_ptr msg_, RpcCallback func_, - unsigned int conn_id_, SendMsgFunc send_response_, - const ConnectionInfo conn_info_) - : name(name_), - msg(msg_), - func(func_), - conn_id(conn_id_), - send_response(send_response_), - conn_info(conn_info_) {} +} // namespace impl - std::string name; - std::shared_ptr msg; - RpcCallback func; - unsigned int conn_id; - SendMsgFunc send_response; - ConnectionInfo conn_info; - }; +class RpcServer : public IRpcServer, + public CallbackManager { + friend class RpcServerTest; + friend class CallbackManager; - std::mutex m_mutex; + public: + RpcServer(int inst, wpi::Logger& logger); - std::queue m_poll_queue; - llvm::DenseMap, SendMsgFunc> - m_response_map; + void Start(); - std::condition_variable m_poll_cond; + unsigned int Add(std::function callback); + unsigned int AddPolled(unsigned int poller_uid); + void RemoveRpc(unsigned int rpc_uid) override; - std::atomic_bool m_terminating; + void ProcessRpc(unsigned int local_id, unsigned int call_uid, StringRef name, + StringRef params, const ConnectionInfo& conn, + SendResponseFunc send_response, + unsigned int rpc_uid) override; - std::function m_on_start; - std::function m_on_exit; + void PostRpcResponse(unsigned int local_id, unsigned int call_uid, + llvm::StringRef result); - ATOMIC_STATIC_DECL(RpcServer) + private: + int m_inst; + wpi::Logger& m_logger; }; } // namespace nt diff --git a/src/main/native/cpp/Storage.cpp b/src/main/native/cpp/Storage.cpp index 4129fdd..82c0aa4 100644 --- a/src/main/native/cpp/Storage.cpp +++ b/src/main/native/cpp/Storage.cpp @@ -14,36 +14,36 @@ #include "llvm/StringExtras.h" #include "support/Base64.h" #include "support/timestamp.h" + +#include "Handle.h" +#include "IDispatcher.h" +#include "IEntryNotifier.h" +#include "IRpcServer.h" #include "Log.h" #include "NetworkConnection.h" using namespace nt; -ATOMIC_STATIC_INIT(Storage) - -Storage::Storage() - : Storage(Notifier::GetInstance(), RpcServer::GetInstance()) {} - -Storage::Storage(Notifier& notifier, RpcServer& rpc_server) - : m_notifier(notifier), m_rpc_server(rpc_server) { +Storage::Storage(IEntryNotifier& notifier, IRpcServer& rpc_server, + wpi::Logger& logger) + : m_notifier(notifier), m_rpc_server(rpc_server), m_logger(logger) { m_terminating = false; } Storage::~Storage() { - Logger::GetInstance().SetLogger(nullptr); m_terminating = true; m_rpc_results_cond.notify_all(); } -void Storage::SetOutgoing(QueueOutgoingFunc queue_outgoing, bool server) { +void Storage::SetDispatcher(IDispatcher* dispatcher, bool server) { std::lock_guard lock(m_mutex); - m_queue_outgoing = queue_outgoing; + m_dispatcher = dispatcher; m_server = server; } -void Storage::ClearOutgoing() { m_queue_outgoing = nullptr; } +void Storage::ClearDispatcher() { m_dispatcher = nullptr; } -NT_Type Storage::GetEntryType(unsigned int id) const { +NT_Type Storage::GetMessageEntryType(unsigned int id) const { std::lock_guard lock(m_mutex); if (id >= m_idmap.size()) return NT_UNASSIGNED; Entry* entry = m_idmap[id]; @@ -75,33 +75,12 @@ void Storage::ProcessIncoming(std::shared_ptr msg, // to be assigned, and we need to send the new assignment back to // the sender as well as all other connections. if (id == 0xffff) { + entry = GetOrNew(name); // see if it was already assigned; ignore if so. - if (m_entries.count(name) != 0) return; - - // create it locally - id = m_idmap.size(); - auto& new_entry = m_entries[name]; - if (!new_entry) new_entry.reset(new Entry(name)); - entry = new_entry.get(); - entry->value = msg->value(); - entry->flags = msg->flags(); - entry->id = id; - m_idmap.push_back(entry); - - // update persistent dirty flag if it's persistent - if (entry->IsPersistent()) m_persistent_dirty = true; - - // notify - m_notifier.NotifyEntry(name, entry->value, NT_NOTIFY_NEW); + if (entry->id != 0xffff) return; - // send the assignment to everyone (including the originator) - if (m_queue_outgoing) { - auto queue_outgoing = m_queue_outgoing; - auto outmsg = Message::EntryAssign(name, id, entry->seq_num.value(), - msg->value(), msg->flags()); - lock.unlock(); - queue_outgoing(outmsg, nullptr, nullptr); - } + entry->flags = msg->flags(); + SetEntryValueImpl(entry, msg->value(), lock, false); return; } if (id >= m_idmap.size() || !m_idmap[id]) { @@ -123,32 +102,30 @@ void Storage::ProcessIncoming(std::shared_ptr msg, entry = m_idmap[id]; if (!entry) { // create local - auto& new_entry = m_entries[name]; - if (!new_entry) { + bool is_new; + entry = GetOrNew(name, &is_new); + entry->id = id; + m_idmap[id] = entry; + if (is_new) { // didn't exist at all (rather than just being a response to a // id assignment request) - new_entry.reset(new Entry(name)); - new_entry->value = msg->value(); - new_entry->flags = msg->flags(); - new_entry->id = id; - m_idmap[id] = new_entry.get(); + entry->value = msg->value(); + entry->flags = msg->flags(); // notify - m_notifier.NotifyEntry(name, new_entry->value, NT_NOTIFY_NEW); + m_notifier.NotifyEntry(entry->local_id, name, entry->value, + NT_NOTIFY_NEW); return; } may_need_update = true; // we may need to send an update message - entry = new_entry.get(); - entry->id = id; - m_idmap[id] = entry; // if the received flags don't match what we sent, we most likely // updated flags locally in the interim; send flags update message. if (msg->flags() != entry->flags) { - auto queue_outgoing = m_queue_outgoing; + auto dispatcher = m_dispatcher; auto outmsg = Message::FlagsUpdate(id, entry->flags); lock.unlock(); - queue_outgoing(outmsg, nullptr, nullptr); + dispatcher->QueueOutgoing(outmsg, nullptr, nullptr); lock.lock(); } } @@ -160,11 +137,11 @@ void Storage::ProcessIncoming(std::shared_ptr msg, SequenceNumber seq_num(msg->seq_num_uid()); if (seq_num < entry->seq_num) { if (may_need_update) { - auto queue_outgoing = m_queue_outgoing; + auto dispatcher = m_dispatcher; auto outmsg = Message::EntryUpdate(entry->id, entry->seq_num.value(), entry->value); lock.unlock(); - queue_outgoing(outmsg, nullptr, nullptr); + dispatcher->QueueOutgoing(outmsg, nullptr, nullptr); } return; } @@ -197,16 +174,16 @@ void Storage::ProcessIncoming(std::shared_ptr msg, entry->seq_num = seq_num; // notify - m_notifier.NotifyEntry(name, entry->value, notify_flags); + m_notifier.NotifyEntry(entry->local_id, name, entry->value, notify_flags); // broadcast to all other connections (note for client there won't // be any other connections, so don't bother) - if (m_server && m_queue_outgoing) { - auto queue_outgoing = m_queue_outgoing; + if (m_server && m_dispatcher) { + auto dispatcher = m_dispatcher; auto outmsg = Message::EntryAssign(entry->name, id, msg->seq_num_uid(), msg->value(), entry->flags); lock.unlock(); - queue_outgoing(outmsg, nullptr, conn); + dispatcher->QueueOutgoing(outmsg, nullptr, conn); } break; } @@ -233,14 +210,15 @@ void Storage::ProcessIncoming(std::shared_ptr msg, if (entry->IsPersistent()) m_persistent_dirty = true; // notify - m_notifier.NotifyEntry(entry->name, entry->value, NT_NOTIFY_UPDATE); + m_notifier.NotifyEntry(entry->local_id, entry->name, entry->value, + NT_NOTIFY_UPDATE); // broadcast to all other connections (note for client there won't // be any other connections, so don't bother) - if (m_server && m_queue_outgoing) { - auto queue_outgoing = m_queue_outgoing; + if (m_server && m_dispatcher) { + auto dispatcher = m_dispatcher; lock.unlock(); - queue_outgoing(msg, nullptr, conn); + dispatcher->QueueOutgoing(msg, nullptr, conn); } break; } @@ -253,27 +231,16 @@ void Storage::ProcessIncoming(std::shared_ptr msg, DEBUG("received flags update to unknown entry"); return; } - Entry* entry = m_idmap[id]; - - // ignore if flags didn't actually change - if (entry->flags == msg->flags()) return; - - // update persistent dirty flag if persistent flag changed - if ((entry->flags & NT_PERSISTENT) != (msg->flags() & NT_PERSISTENT)) - m_persistent_dirty = true; // update local - entry->flags = msg->flags(); - - // notify - m_notifier.NotifyEntry(entry->name, entry->value, NT_NOTIFY_FLAGS); + SetEntryFlagsImpl(m_idmap[id], msg->flags(), lock, false); // broadcast to all other connections (note for client there won't // be any other connections, so don't bother) - if (m_server && m_queue_outgoing) { - auto queue_outgoing = m_queue_outgoing; + if (m_server && m_dispatcher) { + auto dispatcher = m_dispatcher; lock.unlock(); - queue_outgoing(msg, nullptr, conn); + dispatcher->QueueOutgoing(msg, nullptr, conn); } break; } @@ -286,44 +253,29 @@ void Storage::ProcessIncoming(std::shared_ptr msg, DEBUG("received delete to unknown entry"); return; } - Entry* entry = m_idmap[id]; - - // update persistent dirty flag if it's a persistent value - if (entry->IsPersistent()) m_persistent_dirty = true; - - // delete it from idmap - m_idmap[id] = nullptr; - // get entry (as we'll need it for notify) and erase it from the map - // it should always be in the map, but sanity check just in case - auto i = m_entries.find(entry->name); - if (i != m_entries.end()) { - auto entry2 = std::move(i->getValue()); // move the value out - m_entries.erase(i); - - // notify - m_notifier.NotifyEntry(entry2->name, entry2->value, NT_NOTIFY_DELETE); - } + // update local + DeleteEntryImpl(m_idmap[id], m_entries.end(), lock, false); // broadcast to all other connections (note for client there won't // be any other connections, so don't bother) - if (m_server && m_queue_outgoing) { - auto queue_outgoing = m_queue_outgoing; + if (m_server && m_dispatcher) { + auto dispatcher = m_dispatcher; lock.unlock(); - queue_outgoing(msg, nullptr, conn); + dispatcher->QueueOutgoing(msg, nullptr, conn); } break; } case Message::kClearEntries: { // update local - DeleteAllEntriesImpl(); + DeleteAllEntriesImpl(false); // broadcast to all other connections (note for client there won't // be any other connections, so don't bother) - if (m_server && m_queue_outgoing) { - auto queue_outgoing = m_queue_outgoing; + if (m_server && m_dispatcher) { + auto dispatcher = m_dispatcher; lock.unlock(); - queue_outgoing(msg, nullptr, conn); + dispatcher->QueueOutgoing(msg, nullptr, conn); } break; } @@ -338,7 +290,7 @@ void Storage::ProcessIncoming(std::shared_ptr msg, return; } Entry* entry = m_idmap[id]; - if (!entry->value->IsRpc()) { + if (!entry->value || !entry->value->IsRpc()) { lock.unlock(); DEBUG("received RPC call to non-RPC entry"); return; @@ -354,19 +306,34 @@ void Storage::ProcessIncoming(std::shared_ptr msg, conn_info.last_update = 0; conn_info.protocol_version = 0; } - m_rpc_server.ProcessRpc(entry->name, msg, entry->rpc_callback, - conn->uid(), - [=](std::shared_ptr msg) { - auto c = conn_weak.lock(); - if (c) c->QueueOutgoing(msg); - }, - conn_info); + unsigned int call_uid = msg->seq_num_uid(); + m_rpc_server.ProcessRpc( + entry->local_id, call_uid, entry->name, msg->str(), conn_info, + [=](StringRef result) { + auto c = conn_weak.lock(); + if (c) c->QueueOutgoing(Message::RpcResponse(id, call_uid, result)); + }, + entry->rpc_uid); break; } case Message::kRpcResponse: { if (m_server) return; // only process on client + unsigned int id = msg->id(); + if (id >= m_idmap.size() || !m_idmap[id]) { + // ignore response to non-existent RPC + // this can happen due to deleted entries + lock.unlock(); + DEBUG("received rpc response to unknown entry"); + return; + } + Entry* entry = m_idmap[id]; + if (!entry->value || !entry->value->IsRpc()) { + lock.unlock(); + DEBUG("received RPC response to non-RPC entry"); + return; + } m_rpc_results.insert(std::make_pair( - std::make_pair(msg->id(), msg->seq_num_uid()), msg->str())); + RpcIdPair{entry->local_id, msg->seq_num_uid()}, msg->str())); m_rpc_results_cond.notify_all(); break; } @@ -380,7 +347,7 @@ void Storage::GetInitialAssignments( std::lock_guard lock(m_mutex); conn.set_state(NetworkConnection::kSynchronized); for (auto& i : m_entries) { - Entry* entry = i.getValue().get(); + Entry* entry = i.getValue(); msgs->emplace_back(Message::EntryAssign(i.getKey(), entry->id, entry->seq_num.value(), entry->value, entry->flags)); @@ -419,15 +386,16 @@ void Storage::ApplyInitialAssignments( SequenceNumber seq_num(msg->seq_num_uid()); StringRef name = msg->str(); - auto& entry = m_entries[name]; - if (!entry) { + bool is_new; + Entry* entry = GetOrNew(name, &is_new); + if (is_new) { // doesn't currently exist - entry.reset(new Entry(name)); entry->value = msg->value(); entry->flags = msg->flags(); entry->seq_num = seq_num; // notify - m_notifier.NotifyEntry(name, entry->value, NT_NOTIFY_NEW); + m_notifier.NotifyEntry(entry->local_id, name, entry->value, + NT_NOTIFY_NEW); } else { // if reconnect and sequence number not higher than local, then we // don't update the local value and instead send it back to the server @@ -445,76 +413,69 @@ void Storage::ApplyInitialAssignments( entry->flags = msg->flags(); } // notify - m_notifier.NotifyEntry(name, entry->value, notify_flags); + m_notifier.NotifyEntry(entry->local_id, name, entry->value, + notify_flags); } } // set id and save to idmap entry->id = id; if (id >= m_idmap.size()) m_idmap.resize(id + 1); - m_idmap[id] = entry.get(); + m_idmap[id] = entry; } // generate assign messages for unassigned local entries for (auto& i : m_entries) { - Entry* entry = i.getValue().get(); + Entry* entry = i.getValue(); if (entry->id != 0xffff) continue; out_msgs->emplace_back(Message::EntryAssign(entry->name, entry->id, entry->seq_num.value(), entry->value, entry->flags)); } - auto queue_outgoing = m_queue_outgoing; + auto dispatcher = m_dispatcher; lock.unlock(); - for (auto& msg : update_msgs) queue_outgoing(msg, nullptr, nullptr); + for (auto& msg : update_msgs) + dispatcher->QueueOutgoing(msg, nullptr, nullptr); } std::shared_ptr Storage::GetEntryValue(StringRef name) const { std::lock_guard lock(m_mutex); auto i = m_entries.find(name); - return i == m_entries.end() ? nullptr : i->getValue()->value; + if (i == m_entries.end()) return nullptr; + return i->getValue()->value; +} + +std::shared_ptr Storage::GetEntryValue(unsigned int local_id) const { + std::lock_guard lock(m_mutex); + if (local_id >= m_localmap.size()) return nullptr; + return m_localmap[local_id]->value; } bool Storage::SetDefaultEntryValue(StringRef name, std::shared_ptr value) { - if (!value) return false; // can't compare to a null value - if (name.empty()) return false; // can't compare empty name + if (name.empty()) return false; + if (!value) return false; std::unique_lock lock(m_mutex); - auto& new_entry = m_entries[name]; - if (new_entry) { // entry already exists - auto old_value = new_entry->value; - // if types match return true - if (old_value && old_value->type() == value->type()) - return true; - else - return false; // entry exists but doesn't match type - } + Entry* entry = GetOrNew(name); - // if we've gotten here, entry does not exist, and we can write it. - new_entry.reset(new Entry(name)); - Entry* entry = new_entry.get(); - // don't need to compare old value as we know it will assign - entry->value = value; + // we return early if value already exists; if types match return true + if (entry->value) return entry->value->type() == value->type(); - // if we're the server, assign an id if it doesn't have one - if (m_server && entry->id == 0xffff) { - unsigned int id = m_idmap.size(); - entry->id = id; - m_idmap.push_back(entry); - } + SetEntryValueImpl(entry, value, lock, true); + return true; +} - // notify (for local listeners) - if (m_notifier.local_notifiers()) { - // always a new entry if we got this far - m_notifier.NotifyEntry(name, value, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); - } +bool Storage::SetDefaultEntryValue(unsigned int local_id, + std::shared_ptr value) { + if (!value) return false; + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return false; + Entry* entry = m_localmap[local_id].get(); - // generate message - if (!m_queue_outgoing) return true; - auto queue_outgoing = m_queue_outgoing; - auto msg = Message::EntryAssign(name, entry->id, entry->seq_num.value(), - value, entry->flags); - lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); + // we return early if value already exists; if types match return true + if (entry->value) return entry->value->type() == value->type(); + + SetEntryValueImpl(entry, value, lock, true); return true; } @@ -522,12 +483,34 @@ bool Storage::SetEntryValue(StringRef name, std::shared_ptr value) { if (name.empty()) return true; if (!value) return true; std::unique_lock lock(m_mutex); - auto& new_entry = m_entries[name]; - if (!new_entry) new_entry.reset(new Entry(name)); - Entry* entry = new_entry.get(); - auto old_value = entry->value; - if (old_value && old_value->type() != value->type()) + Entry* entry = GetOrNew(name); + + if (entry->value && entry->value->type() != value->type()) + return false; // error on type mismatch + + SetEntryValueImpl(entry, value, lock, true); + return true; +} + +bool Storage::SetEntryValue(unsigned int local_id, + std::shared_ptr value) { + if (!value) return true; + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return true; + Entry* entry = m_localmap[local_id].get(); + + if (entry->value && entry->value->type() != value->type()) return false; // error on type mismatch + + SetEntryValueImpl(entry, value, lock, true); + return true; +} + +void Storage::SetEntryValueImpl(Entry* entry, std::shared_ptr value, + std::unique_lock& lock, + bool local) { + if (!value) return; + auto old_value = entry->value; entry->value = value; // if we're the server, assign an id if it doesn't have one @@ -538,83 +521,55 @@ bool Storage::SetEntryValue(StringRef name, std::shared_ptr value) { } // update persistent dirty flag if value changed and it's persistent - if (entry->IsPersistent() && *old_value != *value) m_persistent_dirty = true; - - // notify (for local listeners) - if (m_notifier.local_notifiers()) { - if (!old_value) - m_notifier.NotifyEntry(name, value, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); - else if (*old_value != *value) - m_notifier.NotifyEntry(name, value, NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL); - } + if (entry->IsPersistent() && (!old_value || *old_value != *value)) + m_persistent_dirty = true; + + // notify + if (!old_value) + m_notifier.NotifyEntry(entry->local_id, entry->name, value, + NT_NOTIFY_NEW | (local ? NT_NOTIFY_LOCAL : 0)); + else if (*old_value != *value) + m_notifier.NotifyEntry(entry->local_id, entry->name, value, + NT_NOTIFY_UPDATE | (local ? NT_NOTIFY_LOCAL : 0)); // generate message - if (!m_queue_outgoing) return true; - auto queue_outgoing = m_queue_outgoing; - if (!old_value) { - auto msg = Message::EntryAssign(name, entry->id, entry->seq_num.value(), - value, entry->flags); + if (!m_dispatcher || (!local && !m_server)) return; + auto dispatcher = m_dispatcher; + if (!old_value || old_value->type() != value->type()) { + if (local) ++entry->seq_num; + auto msg = Message::EntryAssign( + entry->name, entry->id, entry->seq_num.value(), value, entry->flags); lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); + dispatcher->QueueOutgoing(msg, nullptr, nullptr); } else if (*old_value != *value) { - ++entry->seq_num; + if (local) ++entry->seq_num; // don't send an update if we don't have an assigned id yet if (entry->id != 0xffff) { auto msg = Message::EntryUpdate(entry->id, entry->seq_num.value(), value); lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); + dispatcher->QueueOutgoing(msg, nullptr, nullptr); } } - return true; } void Storage::SetEntryTypeValue(StringRef name, std::shared_ptr value) { if (name.empty()) return; if (!value) return; std::unique_lock lock(m_mutex); - auto& new_entry = m_entries[name]; - if (!new_entry) new_entry.reset(new Entry(name)); - Entry* entry = new_entry.get(); - auto old_value = entry->value; - entry->value = value; - if (old_value && *old_value == *value) return; + Entry* entry = GetOrNew(name); - // if we're the server, assign an id if it doesn't have one - if (m_server && entry->id == 0xffff) { - unsigned int id = m_idmap.size(); - entry->id = id; - m_idmap.push_back(entry); - } - - // update persistent dirty flag if it's a persistent value - if (entry->IsPersistent()) m_persistent_dirty = true; + SetEntryValueImpl(entry, value, lock, true); +} - // notify (for local listeners) - if (m_notifier.local_notifiers()) { - if (!old_value) - m_notifier.NotifyEntry(name, value, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); - else - m_notifier.NotifyEntry(name, value, NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL); - } +void Storage::SetEntryTypeValue(unsigned int local_id, + std::shared_ptr value) { + if (!value) return; + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return; + Entry* entry = m_localmap[local_id].get(); + if (!entry) return; - // generate message - if (!m_queue_outgoing) return; - auto queue_outgoing = m_queue_outgoing; - if (!old_value || old_value->type() != value->type()) { - ++entry->seq_num; - auto msg = Message::EntryAssign(name, entry->id, entry->seq_num.value(), - value, entry->flags); - lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); - } else { - ++entry->seq_num; - // don't send an update if we don't have an assigned id yet - if (entry->id != 0xffff) { - auto msg = Message::EntryUpdate(entry->id, entry->seq_num.value(), value); - lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); - } - } + SetEntryValueImpl(entry, value, lock, true); } void Storage::SetEntryFlags(StringRef name, unsigned int flags) { @@ -622,7 +577,18 @@ void Storage::SetEntryFlags(StringRef name, unsigned int flags) { std::unique_lock lock(m_mutex); auto i = m_entries.find(name); if (i == m_entries.end()) return; - Entry* entry = i->getValue().get(); + SetEntryFlagsImpl(i->getValue(), flags, lock, true); +} + +void Storage::SetEntryFlags(unsigned int id_local, unsigned int flags) { + std::unique_lock lock(m_mutex); + if (id_local >= m_localmap.size()) return; + SetEntryFlagsImpl(m_localmap[id_local].get(), flags, lock, true); +} + +void Storage::SetEntryFlagsImpl(Entry* entry, unsigned int flags, + std::unique_lock& lock, + bool local) { if (entry->flags == flags) return; // update persistent dirty flag if persistent flag changed @@ -632,74 +598,108 @@ void Storage::SetEntryFlags(StringRef name, unsigned int flags) { entry->flags = flags; // notify - m_notifier.NotifyEntry(name, entry->value, NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL); + m_notifier.NotifyEntry(entry->local_id, entry->name, entry->value, + NT_NOTIFY_FLAGS | (local ? NT_NOTIFY_LOCAL : 0)); // generate message - if (!m_queue_outgoing) return; - auto queue_outgoing = m_queue_outgoing; + if (!local || !m_dispatcher) return; + auto dispatcher = m_dispatcher; unsigned int id = entry->id; // don't send an update if we don't have an assigned id yet if (id != 0xffff) { lock.unlock(); - queue_outgoing(Message::FlagsUpdate(id, flags), nullptr, nullptr); + dispatcher->QueueOutgoing(Message::FlagsUpdate(id, flags), nullptr, + nullptr); } } unsigned int Storage::GetEntryFlags(StringRef name) const { std::lock_guard lock(m_mutex); auto i = m_entries.find(name); - return i == m_entries.end() ? 0 : i->getValue()->flags; + if (i == m_entries.end()) return 0; + return i->getValue()->flags; +} + +unsigned int Storage::GetEntryFlags(unsigned int local_id) const { + std::lock_guard lock(m_mutex); + if (local_id >= m_localmap.size()) return 0; + return m_localmap[local_id]->flags; } void Storage::DeleteEntry(StringRef name) { std::unique_lock lock(m_mutex); auto i = m_entries.find(name); if (i == m_entries.end()) return; - auto entry = std::move(i->getValue()); + DeleteEntryImpl(i->getValue(), i, lock, true); +} + +void Storage::DeleteEntry(unsigned int local_id) { + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return; + DeleteEntryImpl(m_localmap[local_id].get(), m_entries.end(), lock, true); +} + +void Storage::DeleteEntryImpl(Entry* entry, EntriesMap::iterator it, + std::unique_lock& lock, bool local) { unsigned int id = entry->id; + // Erase entry from name and id mappings. + // Get iterator if it wasn't provided. + if (it == m_entries.end()) it = m_entries.find(entry->name); + if (it != m_entries.end()) m_entries.erase(it); + if (id < m_idmap.size()) m_idmap[id] = nullptr; + + // empty the value and reset id + std::shared_ptr old_value; + old_value.swap(entry->value); + entry->id = 0xffff; + + // remove RPC if there was one + if (entry->rpc_uid != UINT_MAX) { + m_rpc_server.RemoveRpc(entry->rpc_uid); + entry->rpc_uid = UINT_MAX; + } + // update persistent dirty flag if it's a persistent value if (entry->IsPersistent()) m_persistent_dirty = true; - m_entries.erase(i); // erase from map - if (id < m_idmap.size()) m_idmap[id] = nullptr; - - if (!entry->value) return; + if (!old_value) return; // was not previously assigned // notify - m_notifier.NotifyEntry(name, entry->value, - NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL); + m_notifier.NotifyEntry(entry->local_id, entry->name, old_value, + NT_NOTIFY_DELETE | (local ? NT_NOTIFY_LOCAL : 0)); // if it had a value, generate message // don't send an update if we don't have an assigned id yet - if (id != 0xffff) { - if (!m_queue_outgoing) return; - auto queue_outgoing = m_queue_outgoing; + if (local && id != 0xffff) { + if (!m_dispatcher) return; + auto dispatcher = m_dispatcher; lock.unlock(); - queue_outgoing(Message::EntryDelete(id), nullptr, nullptr); + dispatcher->QueueOutgoing(Message::EntryDelete(id), nullptr, nullptr); } } -void Storage::DeleteAllEntriesImpl() { +void Storage::DeleteAllEntriesImpl(bool local) { if (m_entries.empty()) return; // only delete non-persistent values // can't erase without invalidating iterators, so build a new map EntriesMap entries; for (auto& i : m_entries) { - Entry* entry = i.getValue().get(); + Entry* entry = i.getValue(); if (!entry->IsPersistent()) { // notify it's being deleted - if (m_notifier.local_notifiers()) { - m_notifier.NotifyEntry(i.getKey(), i.getValue()->value, - NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL); - } + m_notifier.NotifyEntry(entry->local_id, i.getKey(), entry->value, + NT_NOTIFY_DELETE | (local ? NT_NOTIFY_LOCAL : 0)); // remove it from idmap - if (entry->id != 0xffff) m_idmap[entry->id] = nullptr; - } else { - // add it to new entries - entries.insert(std::make_pair(i.getKey(), std::move(i.getValue()))); + if (entry->id < m_idmap.size()) m_idmap[entry->id] = nullptr; + entry->id = 0xffff; + entry->value.reset(); + continue; } + + // add it to new entries + entries.insert(std::make_pair(i.getKey(), std::move(i.getValue()))); } m_entries.swap(entries); } @@ -708,26 +708,102 @@ void Storage::DeleteAllEntries() { std::unique_lock lock(m_mutex); if (m_entries.empty()) return; - DeleteAllEntriesImpl(); + DeleteAllEntriesImpl(true); // generate message - if (!m_queue_outgoing) return; - auto queue_outgoing = m_queue_outgoing; + if (!m_dispatcher) return; + auto dispatcher = m_dispatcher; lock.unlock(); - queue_outgoing(Message::ClearEntries(), nullptr, nullptr); + dispatcher->QueueOutgoing(Message::ClearEntries(), nullptr, nullptr); +} + +inline Storage::Entry* Storage::GetOrNew(StringRef name, bool* is_new) { + auto& entry = m_entries[name]; + if (!entry) { + if (is_new) *is_new = true; + m_localmap.emplace_back(new Entry(name)); + entry = m_localmap.back().get(); + entry->local_id = m_localmap.size() - 1; + } else { + if (is_new) *is_new = false; + } + return entry; +} + +unsigned int Storage::GetEntry(StringRef name) { + if (name.empty()) return UINT_MAX; + std::unique_lock lock(m_mutex); + return GetOrNew(name)->local_id; +} + +std::vector Storage::GetEntries(StringRef prefix, + unsigned int types) { + std::lock_guard lock(m_mutex); + std::vector ids; + for (auto& i : m_entries) { + if (!i.getKey().startswith(prefix)) continue; + Entry* entry = i.getValue(); + if (types != 0 && (!entry->value || (types & entry->value->type()) == 0)) + continue; + ids.push_back(entry->local_id); + } + return ids; +} + +EntryInfo Storage::GetEntryInfo(int inst, unsigned int local_id) const { + EntryInfo info; + info.entry = 0; + info.type = NT_UNASSIGNED; + info.flags = 0; + info.last_change = 0; + + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return info; + Entry* entry = m_localmap[local_id].get(); + if (!entry->value) return info; + + info.entry = Handle(inst, local_id, Handle::kEntry); + info.name = entry->name; + info.type = entry->value->type(); + info.flags = entry->flags; + info.last_change = entry->value->last_change(); + return info; } -std::vector Storage::GetEntryInfo(StringRef prefix, +std::string Storage::GetEntryName(unsigned int local_id) const { + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return std::string{}; + return m_localmap[local_id]->name; +} + +NT_Type Storage::GetEntryType(unsigned int local_id) const { + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return NT_UNASSIGNED; + Entry* entry = m_localmap[local_id].get(); + if (!entry->value) return NT_UNASSIGNED; + return entry->value->type(); +} + +unsigned long long Storage::GetEntryLastChange(unsigned int local_id) const { + std::unique_lock lock(m_mutex); + if (local_id >= m_localmap.size()) return 0; + Entry* entry = m_localmap[local_id].get(); + if (!entry->value) return 0; + return entry->value->last_change(); +} + +std::vector Storage::GetEntryInfo(int inst, StringRef prefix, unsigned int types) { std::lock_guard lock(m_mutex); std::vector infos; for (auto& i : m_entries) { if (!i.getKey().startswith(prefix)) continue; - Entry* entry = i.getValue().get(); + Entry* entry = i.getValue(); auto value = entry->value; if (!value) continue; if (types != 0 && (types & value->type()) == 0) continue; EntryInfo info; + info.entry = Handle(inst, entry->local_id, Handle::kEntry); info.name = i.getKey(); info.type = value->type(); info.flags = entry->flags; @@ -737,16 +813,6 @@ std::vector Storage::GetEntryInfo(StringRef prefix, return infos; } -void Storage::NotifyEntries(StringRef prefix, - EntryListenerCallback only) const { - std::lock_guard lock(m_mutex); - for (auto& i : m_entries) { - if (!i.getKey().startswith(prefix)) continue; - m_notifier.NotifyEntry(i.getKey(), i.getValue()->value, NT_NOTIFY_IMMEDIATE, - only); - } -} - /* Escapes and writes a string, including start and end double quotes */ static void WriteString(std::ostream& os, llvm::StringRef str) { os << '"'; @@ -791,7 +857,7 @@ bool Storage::GetPersistentEntries( m_persistent_dirty = false; entries->reserve(m_entries.size()); for (auto& i : m_entries) { - Entry* entry = i.getValue().get(); + Entry* entry = i.getValue(); // only write persistent-flagged values if (!entry->IsPersistent()) continue; entries->emplace_back(i.getKey(), entry->value); @@ -1242,9 +1308,7 @@ bool Storage::LoadPersistent( std::vector> msgs; std::unique_lock lock(m_mutex); for (auto& i : entries) { - auto& new_entry = m_entries[i.first]; - if (!new_entry) new_entry.reset(new Entry(i.first)); - Entry* entry = new_entry.get(); + Entry* entry = GetOrNew(i.first); auto old_value = entry->value; entry->value = i.second; bool was_persist = entry->IsPersistent(); @@ -1259,17 +1323,21 @@ bool Storage::LoadPersistent( // notify (for local listeners) if (m_notifier.local_notifiers()) { - if (!old_value) - m_notifier.NotifyEntry(i.first, i.second, + if (!old_value) { + m_notifier.NotifyEntry(entry->local_id, i.first, i.second, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); - else if (*old_value != *i.second) { + } else if (*old_value != *i.second) { unsigned int notify_flags = NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL; if (!was_persist) notify_flags |= NT_NOTIFY_FLAGS; - m_notifier.NotifyEntry(i.first, i.second, notify_flags); + m_notifier.NotifyEntry(entry->local_id, i.first, i.second, + notify_flags); + } else if (!was_persist) { + m_notifier.NotifyEntry(entry->local_id, i.first, i.second, + NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL); } } - if (!m_queue_outgoing) continue; // shortcut + if (!m_dispatcher) continue; // shortcut ++entry->seq_num; // put on update queue @@ -1287,10 +1355,11 @@ bool Storage::LoadPersistent( } } - if (m_queue_outgoing) { - auto queue_outgoing = m_queue_outgoing; + if (m_dispatcher) { + auto dispatcher = m_dispatcher; lock.unlock(); - for (auto& msg : msgs) queue_outgoing(std::move(msg), nullptr, nullptr); + for (auto& msg : msgs) + dispatcher->QueueOutgoing(std::move(msg), nullptr, nullptr); } } @@ -1306,23 +1375,18 @@ const char* Storage::LoadPersistent( return nullptr; } -void Storage::CreateRpc(StringRef name, StringRef def, RpcCallback callback) { - if (name.empty() || def.empty() || !callback) return; +void Storage::CreateRpc(unsigned int local_id, StringRef def, + unsigned int rpc_uid) { std::unique_lock lock(m_mutex); - if (!m_server) return; // only server can create RPCs + if (local_id >= m_localmap.size()) return; + Entry* entry = m_localmap[local_id].get(); - auto& new_entry = m_entries[name]; - if (!new_entry) new_entry.reset(new Entry(name)); - Entry* entry = new_entry.get(); auto old_value = entry->value; auto value = Value::MakeRpc(def); entry->value = value; - // set up the new callback - entry->rpc_callback = callback; - - // start the RPC server - m_rpc_server.Start(); + // set up the RPC info + entry->rpc_uid = rpc_uid; if (old_value && *old_value == *value) return; @@ -1334,78 +1398,40 @@ void Storage::CreateRpc(StringRef name, StringRef def, RpcCallback callback) { } // generate message - if (!m_queue_outgoing) return; - auto queue_outgoing = m_queue_outgoing; + if (!m_dispatcher) return; + auto dispatcher = m_dispatcher; if (!old_value || old_value->type() != value->type()) { ++entry->seq_num; - auto msg = Message::EntryAssign(name, entry->id, entry->seq_num.value(), - value, entry->flags); + auto msg = Message::EntryAssign( + entry->name, entry->id, entry->seq_num.value(), value, entry->flags); lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); + dispatcher->QueueOutgoing(msg, nullptr, nullptr); } else { ++entry->seq_num; auto msg = Message::EntryUpdate(entry->id, entry->seq_num.value(), value); lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); + dispatcher->QueueOutgoing(msg, nullptr, nullptr); } } -void Storage::CreatePolledRpc(StringRef name, StringRef def) { - if (name.empty() || def.empty()) return; +unsigned int Storage::CallRpc(unsigned int local_id, StringRef params) { std::unique_lock lock(m_mutex); - if (!m_server) return; // only server can create RPCs + if (local_id >= m_localmap.size()) return 0; + Entry* entry = m_localmap[local_id].get(); - auto& new_entry = m_entries[name]; - if (!new_entry) new_entry.reset(new Entry(name)); - Entry* entry = new_entry.get(); - auto old_value = entry->value; - auto value = Value::MakeRpc(def); - entry->value = value; - - // a nullptr callback indicates a polled RPC - entry->rpc_callback = nullptr; - - if (old_value && *old_value == *value) return; - - // assign an id if it doesn't have one - if (entry->id == 0xffff) { - unsigned int id = m_idmap.size(); - entry->id = id; - m_idmap.push_back(entry); - } - - // generate message - if (!m_queue_outgoing) return; - auto queue_outgoing = m_queue_outgoing; - if (!old_value || old_value->type() != value->type()) { - ++entry->seq_num; - auto msg = Message::EntryAssign(name, entry->id, entry->seq_num.value(), - value, entry->flags); - lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); - } else { - ++entry->seq_num; - auto msg = Message::EntryUpdate(entry->id, entry->seq_num.value(), value); - lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); - } -} - -unsigned int Storage::CallRpc(StringRef name, StringRef params) { - std::unique_lock lock(m_mutex); - auto i = m_entries.find(name); - if (i == m_entries.end()) return 0; - auto& entry = i->getValue(); - if (!entry->value->IsRpc()) return 0; + if (!entry->value || !entry->value->IsRpc()) return 0; ++entry->rpc_call_uid; if (entry->rpc_call_uid > 0xffff) entry->rpc_call_uid = 0; - unsigned int combined_uid = (entry->id << 16) | entry->rpc_call_uid; - auto msg = Message::ExecuteRpc(entry->id, entry->rpc_call_uid, params); + unsigned int call_uid = entry->rpc_call_uid; + + auto msg = Message::ExecuteRpc(entry->id, call_uid, params); + StringRef name{entry->name}; + if (m_server) { // RPCs are unlikely to be used locally on the server, but handle it // gracefully anyway. - auto rpc_callback = entry->rpc_callback; + auto rpc_uid = entry->rpc_uid; lock.unlock(); ConnectionInfo conn_info; conn_info.remote_id = "Server"; @@ -1413,79 +1439,86 @@ unsigned int Storage::CallRpc(StringRef name, StringRef params) { conn_info.remote_port = 0; conn_info.last_update = wpi::Now(); conn_info.protocol_version = 0x0300; - m_rpc_server.ProcessRpc( - name, msg, rpc_callback, 0xffffU, - [this](std::shared_ptr msg) { - std::lock_guard lock(m_mutex); - m_rpc_results.insert(std::make_pair( - std::make_pair(msg->id(), msg->seq_num_uid()), msg->str())); - m_rpc_results_cond.notify_all(); - }, - conn_info); + unsigned int call_uid = msg->seq_num_uid(); + m_rpc_server.ProcessRpc(local_id, call_uid, name, msg->str(), conn_info, + [=](StringRef result) { + std::lock_guard lock(m_mutex); + m_rpc_results.insert(std::make_pair( + RpcIdPair{local_id, call_uid}, result)); + m_rpc_results_cond.notify_all(); + }, + rpc_uid); } else { - auto queue_outgoing = m_queue_outgoing; + auto dispatcher = m_dispatcher; lock.unlock(); - queue_outgoing(msg, nullptr, nullptr); + dispatcher->QueueOutgoing(msg, nullptr, nullptr); } - return combined_uid; + return call_uid; } -bool Storage::GetRpcResult(bool blocking, unsigned int call_uid, +bool Storage::GetRpcResult(unsigned int local_id, unsigned int call_uid, std::string* result) { - return GetRpcResult(blocking, call_uid, -1, result); + bool timed_out = false; + return GetRpcResult(local_id, call_uid, result, -1, &timed_out); } -bool Storage::GetRpcResult(bool blocking, unsigned int call_uid, - double time_out, std::string* result) { +bool Storage::GetRpcResult(unsigned int local_id, unsigned int call_uid, + std::string* result, double timeout, + bool* timed_out) { std::unique_lock lock(m_mutex); + + RpcIdPair call_pair{local_id, call_uid}; + // only allow one blocking call per rpc call uid - if (!m_rpc_blocking_calls.insert(call_uid).second) return false; + if (!m_rpc_blocking_calls.insert(call_pair).second) return false; + #if defined(_MSC_VER) && _MSC_VER < 1900 auto timeout_time = std::chrono::steady_clock::now() + std::chrono::duration( - static_cast(time_out * 1e9)); + static_cast(timeout * 1e9)); #else - auto timeout_time = std::chrono::steady_clock::now() + - std::chrono::duration(time_out); + auto timeout_time = + std::chrono::steady_clock::now() + std::chrono::duration(timeout); #endif + *timed_out = false; for (;;) { - auto i = - m_rpc_results.find(std::make_pair(call_uid >> 16, call_uid & 0xffff)); + auto i = m_rpc_results.find(call_pair); if (i == m_rpc_results.end()) { - if (!blocking || m_terminating) { - m_rpc_blocking_calls.erase(call_uid); + if (timeout == 0 || m_terminating) { + m_rpc_blocking_calls.erase(call_pair); return false; } - if (time_out < 0) { + if (timeout < 0) { m_rpc_results_cond.wait(lock); } else { - auto timed_out = m_rpc_results_cond.wait_until(lock, timeout_time); - if (timed_out == std::cv_status::timeout) { - m_rpc_blocking_calls.erase(call_uid); + auto cond_timed_out = m_rpc_results_cond.wait_until(lock, timeout_time); + if (cond_timed_out == std::cv_status::timeout) { + m_rpc_blocking_calls.erase(call_pair); + *timed_out = true; return false; } } // if element does not exist, we have been canceled - if (m_rpc_blocking_calls.count(call_uid) == 0) { + if (m_rpc_blocking_calls.count(call_pair) == 0) { return false; } if (m_terminating) { - m_rpc_blocking_calls.erase(call_uid); + m_rpc_blocking_calls.erase(call_pair); return false; } continue; } result->swap(i->getSecond()); // safe to erase even if id does not exist - m_rpc_blocking_calls.erase(call_uid); + m_rpc_blocking_calls.erase(call_pair); m_rpc_results.erase(i); return true; } } -void Storage::CancelBlockingRpcResult(unsigned int call_uid) { +void Storage::CancelRpcResult(unsigned int local_id, unsigned int call_uid) { std::unique_lock lock(m_mutex); // safe to erase even if id does not exist - m_rpc_blocking_calls.erase(call_uid); + m_rpc_blocking_calls.erase(RpcIdPair{local_id, call_uid}); m_rpc_results_cond.notify_all(); } diff --git a/src/main/native/cpp/Storage.h b/src/main/native/cpp/Storage.h index 9e7d23f..096ef87 100644 --- a/src/main/native/cpp/Storage.h +++ b/src/main/native/cpp/Storage.h @@ -9,84 +9,107 @@ #define NT_STORAGE_H_ #include +#include #include #include #include #include #include #include -#include #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 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 msg, NetworkConnection* conn, - std::weak_ptr conn_weak); - void GetInitialAssignments(NetworkConnection& conn, - std::vector>* msgs); - void ApplyInitialAssignments(NetworkConnection& conn, - llvm::ArrayRef> msgs, - bool new_server, - std::vector>* out_msgs); + std::weak_ptr conn_weak) override; + void GetInitialAssignments( + NetworkConnection& conn, + std::vector>* msgs) override; + void ApplyInitialAssignments( + NetworkConnection& conn, llvm::ArrayRef> msgs, + bool new_server, + std::vector>* out_msgs) override; // User functions. These are the actual implementations of the corresponding // user API functions in ntcore_cpp. std::shared_ptr GetEntryValue(StringRef name) const; + std::shared_ptr GetEntryValue(unsigned int local_id) const; + bool SetDefaultEntryValue(StringRef name, std::shared_ptr value); + bool SetDefaultEntryValue(unsigned int local_id, + std::shared_ptr value); + bool SetEntryValue(StringRef name, std::shared_ptr value); + bool SetEntryValue(unsigned int local_id, std::shared_ptr value); + void SetEntryTypeValue(StringRef name, std::shared_ptr value); + void SetEntryTypeValue(unsigned int local_id, std::shared_ptr 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 GetEntryInfo(StringRef prefix, unsigned int types); - void NotifyEntries(StringRef prefix, - EntryListenerCallback only = nullptr) const; + + std::vector GetEntryInfo(int inst, StringRef prefix, + unsigned int types); + + // Index-only + unsigned int GetEntry(StringRef name); + std::vector 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 warn); + std::function 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; - 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> EntriesMap; + typedef llvm::StringMap EntriesMap; typedef std::vector IdMap; - typedef llvm::DenseMap, std::string> - RpcResultMap; - typedef llvm::SmallSet RpcBlockingCallSet; + typedef std::vector> LocalMap; + typedef std::pair RpcIdPair; + typedef llvm::DenseMap RpcResultMap; + typedef llvm::SmallSet 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>>* entries) const; - void DeleteAllEntriesImpl(); - - ATOMIC_STATIC_DECL(Storage) + void SetEntryValueImpl(Entry* entry, std::shared_ptr value, + std::unique_lock& lock, bool local); + void SetEntryFlagsImpl(Entry* entry, unsigned int flags, + std::unique_lock& lock, bool local); + void DeleteEntryImpl(Entry* entry, EntriesMap::iterator it, + std::unique_lock& lock, bool local); + + // Must be called with m_mutex held + void DeleteAllEntriesImpl(bool local); + Entry* GetOrNew(StringRef name, bool* is_new = nullptr); }; } // namespace nt diff --git a/src/main/native/cpp/Value.cpp b/src/main/native/cpp/Value.cpp index ae9d779..5e065cb 100644 --- a/src/main/native/cpp/Value.cpp +++ b/src/main/native/cpp/Value.cpp @@ -5,7 +5,7 @@ /* the project. */ /*----------------------------------------------------------------------------*/ -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" #include "Value_internal.h" #include "support/timestamp.h" @@ -16,9 +16,12 @@ Value::Value() { m_val.last_change = wpi::Now(); } -Value::Value(NT_Type type, const private_init&) { +Value::Value(NT_Type type, unsigned long long time, const private_init&) { m_val.type = type; - m_val.last_change = wpi::Now(); + if (time == 0) + m_val.last_change = wpi::Now(); + else + m_val.last_change = time; if (m_val.type == NT_BOOLEAN_ARRAY) m_val.data.arr_boolean.arr = nullptr; else if (m_val.type == NT_DOUBLE_ARRAY) @@ -36,16 +39,18 @@ Value::~Value() { delete[] m_val.data.arr_string.arr; } -std::shared_ptr Value::MakeBooleanArray(llvm::ArrayRef value) { - auto val = std::make_shared(NT_BOOLEAN_ARRAY, private_init()); +std::shared_ptr Value::MakeBooleanArray(llvm::ArrayRef value, + unsigned long long time) { + auto val = std::make_shared(NT_BOOLEAN_ARRAY, time, private_init()); val->m_val.data.arr_boolean.arr = new int[value.size()]; val->m_val.data.arr_boolean.size = value.size(); std::copy(value.begin(), value.end(), val->m_val.data.arr_boolean.arr); return val; } -std::shared_ptr Value::MakeDoubleArray(llvm::ArrayRef value) { - auto val = std::make_shared(NT_DOUBLE_ARRAY, private_init()); +std::shared_ptr Value::MakeDoubleArray(llvm::ArrayRef value, + unsigned long long time) { + auto val = std::make_shared(NT_DOUBLE_ARRAY, time, private_init()); val->m_val.data.arr_double.arr = new double[value.size()]; val->m_val.data.arr_double.size = value.size(); std::copy(value.begin(), value.end(), val->m_val.data.arr_double.arr); @@ -53,8 +58,8 @@ std::shared_ptr Value::MakeDoubleArray(llvm::ArrayRef value) { } std::shared_ptr Value::MakeStringArray( - llvm::ArrayRef value) { - auto val = std::make_shared(NT_STRING_ARRAY, private_init()); + llvm::ArrayRef value, unsigned long long time) { + auto val = std::make_shared(NT_STRING_ARRAY, time, private_init()); val->m_string_array = value; // point NT_Value to the contents in the vector. val->m_val.data.arr_string.arr = new NT_String[value.size()]; @@ -67,8 +72,8 @@ std::shared_ptr Value::MakeStringArray( } std::shared_ptr Value::MakeStringArray( - std::vector&& value) { - auto val = std::make_shared(NT_STRING_ARRAY, private_init()); + std::vector&& value, unsigned long long time) { + auto val = std::make_shared(NT_STRING_ARRAY, time, private_init()); val->m_string_array = std::move(value); value.clear(); // point NT_Value to the contents in the vector. diff --git a/src/main/native/cpp/WireDecoder.cpp b/src/main/native/cpp/WireDecoder.cpp index 2af12ce..6a087e1 100644 --- a/src/main/native/cpp/WireDecoder.cpp +++ b/src/main/native/cpp/WireDecoder.cpp @@ -45,8 +45,9 @@ static double ReadDouble(const char*& buf) { return llvm::BitsToDouble(val); } -WireDecoder::WireDecoder(wpi::raw_istream& is, unsigned int proto_rev) - : m_is(is) { +WireDecoder::WireDecoder(wpi::raw_istream& is, unsigned int proto_rev, + wpi::Logger& logger) + : m_is(is), m_logger(logger) { // Start with a 1K temporary buffer. Use malloc instead of new so we can // realloc. m_allocated = 1024; diff --git a/src/main/native/cpp/WireDecoder.h b/src/main/native/cpp/WireDecoder.h index b428fa3..c9d45af 100644 --- a/src/main/native/cpp/WireDecoder.h +++ b/src/main/native/cpp/WireDecoder.h @@ -10,10 +10,10 @@ #include -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" #include "support/leb128.h" #include "support/raw_istream.h" -//#include "Log.h" +#include "Log.h" namespace nt { @@ -26,7 +26,8 @@ namespace nt { */ class WireDecoder { public: - explicit WireDecoder(wpi::raw_istream& is, unsigned int proto_rev); + WireDecoder(wpi::raw_istream& is, unsigned int proto_rev, + wpi::Logger& logger); ~WireDecoder(); void set_proto_rev(unsigned int proto_rev) { m_proto_rev = proto_rev; } @@ -34,6 +35,9 @@ class WireDecoder { /* Get the active protocol revision. */ unsigned int proto_rev() const { return m_proto_rev; } + /* Get the logger. */ + wpi::Logger& logger() const { return m_logger; } + /* Clears error indicator. */ void Reset() { m_error = nullptr; } @@ -54,8 +58,7 @@ class WireDecoder { *buf = m_buf; m_is.read(m_buf, len); #if 0 - nt::Logger& logger = nt::Logger::GetInstance(); - if (logger.min_level() <= NT_LOG_DEBUG4 && logger.HasLogger()) { + if (m_logger.min_level() <= NT_LOG_DEBUG4 && m_logger.HasLogger()) { std::ostringstream oss; oss << "read " << len << " bytes:" << std::hex; if (!rv) @@ -64,7 +67,7 @@ class WireDecoder { for (std::size_t i=0; i < len; ++i) oss << ' ' << (unsigned int)((*buf)[i]); } - logger.Log(NT_LOG_DEBUG4, __FILE__, __LINE__, oss.str().c_str()); + m_logger.Log(NT_LOG_DEBUG4, __FILE__, __LINE__, oss.str().c_str()); } #endif return !m_is.has_error(); @@ -135,6 +138,9 @@ class WireDecoder { /* input stream */ wpi::raw_istream& m_is; + /* logger */ + wpi::Logger& m_logger; + /* temporary buffer */ char* m_buf; diff --git a/src/main/native/cpp/WireEncoder.h b/src/main/native/cpp/WireEncoder.h index de122a6..48ea9b6 100644 --- a/src/main/native/cpp/WireEncoder.h +++ b/src/main/native/cpp/WireEncoder.h @@ -13,7 +13,7 @@ #include "llvm/SmallVector.h" #include "llvm/StringRef.h" -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" namespace nt { diff --git a/src/main/native/cpp/jni/NetworkTablesJNI.cpp b/src/main/native/cpp/jni/NetworkTablesJNI.cpp index ec5bf1f..d94c7c5 100644 --- a/src/main/native/cpp/jni/NetworkTablesJNI.cpp +++ b/src/main/native/cpp/jni/NetworkTablesJNI.cpp @@ -1,23 +1,19 @@ #include -#include #include -#include -#include -#include -#include -#include -#include "edu_wpi_first_wpilibj_networktables_NetworkTablesJNI.h" +#include "edu_wpi_first_networktables_NetworkTablesJNI.h" #include "ntcore.h" -#include "support/atomic_static.h" #include "support/jni_util.h" -#include "support/SafeThread.h" #include "llvm/ConvertUTF.h" #include "llvm/SmallString.h" -#include "llvm/SmallVector.h" +#include "llvm/raw_ostream.h" using namespace wpi::java; +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + // // Globals and load/unload // @@ -25,34 +21,18 @@ using namespace wpi::java; // Used for callback. static JavaVM *jvm = nullptr; static JClass booleanCls; -static JClass doubleCls; static JClass connectionInfoCls; +static JClass connectionNotificationCls; +static JClass doubleCls; static JClass entryInfoCls; -static JException persistentEx; +static JClass entryNotificationCls; +static JClass logMessageCls; +static JClass rpcAnswerCls; +static JClass valueCls; static JException illegalArgEx; +static JException interruptedEx; static JException nullPointerEx; -// Thread-attached environment for listener callbacks. -static JNIEnv *listenerEnv = nullptr; - -static void ListenerOnStart() { - if (!jvm) return; - JNIEnv *env; - JavaVMAttachArgs args; - args.version = JNI_VERSION_1_2; - args.name = const_cast("NTListener"); - args.group = nullptr; - if (jvm->AttachCurrentThreadAsDaemon(reinterpret_cast(&env), - &args) != JNI_OK) - return; - if (!env || !env->functions) return; - listenerEnv = env; -} - -static void ListenerOnExit() { - listenerEnv = nullptr; - if (!jvm) return; - jvm->DetachCurrentThread(); -} +static JException persistentEx; extern "C" { @@ -67,29 +47,44 @@ JNIEXPORT jint JNICALL JNI_OnLoad(JavaVM *vm, void *reserved) { booleanCls = JClass(env, "java/lang/Boolean"); if (!booleanCls) return JNI_ERR; - doubleCls = JClass(env, "java/lang/Double"); - if (!doubleCls) return JNI_ERR; - connectionInfoCls = - JClass(env, "edu/wpi/first/wpilibj/networktables/ConnectionInfo"); + JClass(env, "edu/wpi/first/networktables/ConnectionInfo"); if (!connectionInfoCls) return JNI_ERR; - entryInfoCls = JClass(env, "edu/wpi/first/wpilibj/networktables/EntryInfo"); + connectionNotificationCls = JClass(env, "edu/wpi/first/networktables/ConnectionNotification"); + if (!connectionNotificationCls) return JNI_ERR; + + doubleCls = JClass(env, "java/lang/Double"); + if (!doubleCls) return JNI_ERR; + + entryInfoCls = JClass(env, "edu/wpi/first/networktables/EntryInfo"); if (!entryInfoCls) return JNI_ERR; - persistentEx = JException( - env, "edu/wpi/first/wpilibj/networktables/PersistentException"); - if (!persistentEx) return JNI_ERR; + entryNotificationCls = JClass(env, "edu/wpi/first/networktables/EntryNotification"); + if (!entryNotificationCls) return JNI_ERR; + + logMessageCls = JClass(env, "edu/wpi/first/networktables/LogMessage"); + if (!logMessageCls) return JNI_ERR; + + rpcAnswerCls = JClass(env, "edu/wpi/first/networktables/RpcAnswer"); + if (!rpcAnswerCls) return JNI_ERR; + + valueCls = + JClass(env, "edu/wpi/first/networktables/NetworkTableValue"); + if (!valueCls) return JNI_ERR; illegalArgEx = JException(env, "java/lang/IllegalArgumentException"); if (!illegalArgEx) return JNI_ERR; + interruptedEx = JException(env, "java/lang/InterruptedException"); + if (!interruptedEx) return JNI_ERR; + nullPointerEx = JException(env, "java/lang/NullPointerException"); if (!nullPointerEx) return JNI_ERR; - // Initial configuration of listener start/exit - nt::SetListenerOnStart(ListenerOnStart); - nt::SetListenerOnExit(ListenerOnExit); + persistentEx = JException( + env, "edu/wpi/first/networktables/PersistentException"); + if (!persistentEx) return JNI_ERR; return JNI_VERSION_1_6; } @@ -100,105 +95,50 @@ JNIEXPORT void JNICALL JNI_OnUnload(JavaVM *vm, void *reserved) { return; // Delete global references booleanCls.free(env); - doubleCls.free(env); connectionInfoCls.free(env); + connectionNotificationCls.free(env); + doubleCls.free(env); entryInfoCls.free(env); - persistentEx.free(env); + entryNotificationCls.free(env); + logMessageCls.free(env); + rpcAnswerCls.free(env); + valueCls.free(env); illegalArgEx.free(env); + interruptedEx.free(env); nullPointerEx.free(env); + persistentEx.free(env); jvm = nullptr; } } // extern "C" -// -// Helper class to create and clean up a global reference -// -template -class JGlobal { - public: - JGlobal(JNIEnv *env, T obj) - : m_obj(static_cast(env->NewGlobalRef(obj))) {} - ~JGlobal() { - if (!jvm || nt::NotifierDestroyed()) return; - JNIEnv *env; - bool attached = false; - // don't attach and de-attach if already attached to a thread. - if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == - JNI_EDETACHED) { - if (jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != - JNI_OK) - return; - attached = true; - } - if (!env || !env->functions) return; - env->DeleteGlobalRef(m_obj); - if (attached) jvm->DetachCurrentThread(); - } - operator T() { return m_obj; } - T obj() { return m_obj; } - - private: - T m_obj; -}; - -// -// Helper class to create and clean up a weak global reference -// -template -class JWeakGlobal { - public: - JWeakGlobal(JNIEnv *env, T obj) - : m_obj(static_cast(env->NewWeakGlobalRef(obj))) {} - ~JWeakGlobal() { - if (!jvm || nt::NotifierDestroyed()) return; - JNIEnv *env; - bool attached = false; - // don't attach and de-attach if already attached to a thread. - if (jvm->GetEnv(reinterpret_cast(&env), JNI_VERSION_1_6) == - JNI_EDETACHED) { - if (jvm->AttachCurrentThread(reinterpret_cast(&env), nullptr) != - JNI_OK) - return; - attached = true; - } - if (!env || !env->functions) return; - env->DeleteWeakGlobalRef(m_obj); - if (attached) jvm->DetachCurrentThread(); - } - JLocal obj(JNIEnv *env) { - return JLocal{env, env->NewLocalRef(m_obj)}; - } - - private: - T m_obj; -}; - // // Conversions from Java objects to C++ // -inline std::shared_ptr FromJavaRaw(JNIEnv *env, jbyteArray jarr) { +inline std::shared_ptr FromJavaRaw(JNIEnv *env, jbyteArray jarr, + jlong time) { CriticalJByteArrayRef ref{env, jarr}; if (!ref) return nullptr; - return nt::Value::MakeRaw(ref); + return nt::Value::MakeRaw(ref, time); } -inline std::shared_ptr FromJavaRawBB(JNIEnv *env, jobject jbb, - int len) { +inline std::shared_ptr FromJavaRawBB(JNIEnv* env, jobject jbb, + int len, jlong time) { JByteArrayRef ref{env, jbb, len}; if (!ref) return nullptr; - return nt::Value::MakeRaw(ref.str()); + return nt::Value::MakeRaw(ref.str(), time); } -inline std::shared_ptr FromJavaRpc(JNIEnv *env, jbyteArray jarr) { +inline std::shared_ptr FromJavaRpc(JNIEnv *env, jbyteArray jarr, + jlong time) { CriticalJByteArrayRef ref{env, jarr}; if (!ref) return nullptr; - return nt::Value::MakeRpc(ref.str()); + return nt::Value::MakeRpc(ref.str(), time); } -std::shared_ptr FromJavaBooleanArray(JNIEnv *env, - jbooleanArray jarr) { +std::shared_ptr FromJavaBooleanArray(JNIEnv* env, jbooleanArray jarr, + jlong time) { CriticalJBooleanArrayRef ref{env, jarr}; if (!ref) return nullptr; llvm::ArrayRef elements{ref}; @@ -206,16 +146,18 @@ std::shared_ptr FromJavaBooleanArray(JNIEnv *env, std::vector arr; arr.reserve(len); for (size_t i = 0; i < len; ++i) arr.push_back(elements[i]); - return nt::Value::MakeBooleanArray(arr); + return nt::Value::MakeBooleanArray(arr, time); } -std::shared_ptr FromJavaDoubleArray(JNIEnv *env, jdoubleArray jarr) { +std::shared_ptr FromJavaDoubleArray(JNIEnv* env, jdoubleArray jarr, + jlong time) { CriticalJDoubleArrayRef ref{env, jarr}; if (!ref) return nullptr; - return nt::Value::MakeDoubleArray(ref); + return nt::Value::MakeDoubleArray(ref, time); } -std::shared_ptr FromJavaStringArray(JNIEnv *env, jobjectArray jarr) { +std::shared_ptr FromJavaStringArray(JNIEnv* env, jobjectArray jarr, + jlong time) { size_t len = env->GetArrayLength(jarr); std::vector arr; arr.reserve(len); @@ -225,7 +167,7 @@ std::shared_ptr FromJavaStringArray(JNIEnv *env, jobjectArray jarr) { if (!elem) return nullptr; arr.push_back(JStringRef{env, elem}.str()); } - return nt::Value::MakeStringArray(std::move(arr)); + return nt::Value::MakeStringArray(std::move(arr), time); } // @@ -264,6 +206,16 @@ static jobject MakeJObject(JNIEnv *env, const nt::Value& value) { } } +static jobject MakeJValue(JNIEnv *env, const nt::Value* value) { + static jmethodID constructor = + env->GetMethodID(valueCls, "", "(ILjava/lang/Object;J)V"); + if (!value) + return env->NewObject(valueCls, constructor, (jint)NT_UNASSIGNED, nullptr, + (jlong)0); + return env->NewObject(valueCls, constructor, (jint)value->type(), + MakeJObject(env, *value), (jlong)value->time()); +} + static jobject MakeJObject(JNIEnv *env, const nt::ConnectionInfo &info) { static jmethodID constructor = env->GetMethodID(connectionInfoCls, "", @@ -275,942 +227,1080 @@ static jobject MakeJObject(JNIEnv *env, const nt::ConnectionInfo &info) { (jlong)info.last_update, (jint)info.protocol_version); } -static jobject MakeJObject(JNIEnv *env, const nt::EntryInfo &info) { +static jobject MakeJObject(JNIEnv *env, jobject inst, + const nt::ConnectionNotification ¬ification) { + static jmethodID constructor = + env->GetMethodID(connectionNotificationCls, "", + "(Ledu/wpi/first/networktables/NetworkTableInstance;IZLedu/wpi/first/networktables/ConnectionInfo;)V"); + JLocal conn{env, MakeJObject(env, notification.conn)}; + return env->NewObject(connectionNotificationCls, constructor, inst, + (jint)notification.listener, + (jboolean)notification.connected, conn.obj()); +} + +static jobject MakeJObject(JNIEnv *env, jobject inst, + const nt::EntryInfo &info) { static jmethodID constructor = - env->GetMethodID(entryInfoCls, "", "(Ljava/lang/String;IIJ)V"); + env->GetMethodID(entryInfoCls, "", "(Ledu/wpi/first/networktables/NetworkTableInstance;ILjava/lang/String;IIJ)V"); JLocal name{env, MakeJString(env, info.name)}; - return env->NewObject(entryInfoCls, constructor, name.obj(), (jint)info.type, - (jint)info.flags, (jlong)info.last_change); + return env->NewObject(entryInfoCls, constructor, inst, (jint)info.entry, + name.obj(), (jint)info.type, (jint)info.flags, + (jlong)info.last_change); } -extern "C" { +static jobject MakeJObject(JNIEnv *env, jobject inst, + const nt::EntryNotification ¬ification) { + static jmethodID constructor = + env->GetMethodID(entryNotificationCls, "", + "(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/String;Ledu/wpi/first/networktables/NetworkTableValue;I)V"); + JLocal name{env, MakeJString(env, notification.name)}; + JLocal value{env, MakeJValue(env, notification.value.get())}; + return env->NewObject(entryNotificationCls, constructor, inst, + (jint)notification.listener, (jint)notification.entry, + name.obj(), value.obj(), (jint)notification.flags); +} -/* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: containsKey - * Signature: (Ljava/lang/String;)Z - */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_containsKey - (JNIEnv *env, jclass, jstring key) -{ - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; +static jobject MakeJObject(JNIEnv *env, jobject inst, + const nt::LogMessage &msg) { + static jmethodID constructor = + env->GetMethodID(logMessageCls, "", + "(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/String;ILjava/lang/String;)V"); + JLocal filename{env, MakeJString(env, msg.filename)}; + JLocal message{env, MakeJString(env, msg.message)}; + return env->NewObject(logMessageCls, constructor, inst, (jint)msg.logger, + (jint)msg.level, filename.obj(), (jint)msg.line, + message.obj()); +} + +static jobject MakeJObject(JNIEnv *env, jobject inst, + const nt::RpcAnswer &answer) { + static jmethodID constructor = + env->GetMethodID(rpcAnswerCls, "", + "(Ledu/wpi/first/networktables/NetworkTableInstance;IILjava/lang/String;Ljava/lang/String;Ledu/wpi/first/networktables/ConnectionInfo;)V"); + JLocal name{env, MakeJString(env, answer.name)}; + JLocal params{env, MakeJString(env, answer.params)}; + JLocal conn{env, MakeJObject(env, answer.conn)}; + return env->NewObject(rpcAnswerCls, constructor, inst, (jint)answer.entry, + (jint)answer.call, name.obj(), params.obj(), + conn.obj()); +} + +static jobjectArray MakeJObject(JNIEnv *env, jobject inst, + llvm::ArrayRef arr) { + jobjectArray jarr = env->NewObjectArray(arr.size(), connectionNotificationCls, nullptr); + if (!jarr) return nullptr; + for (size_t i = 0; i < arr.size(); ++i) { + JLocal elem{env, MakeJObject(env, inst, arr[i])}; + env->SetObjectArrayElement(jarr, i, elem.obj()); } - auto val = nt::GetEntryValue(JStringRef{env, key}); - if (!val) return false; - return true; + return jarr; +} + +static jobjectArray MakeJObject(JNIEnv *env, jobject inst, + llvm::ArrayRef arr) { + jobjectArray jarr = env->NewObjectArray(arr.size(), entryNotificationCls, nullptr); + if (!jarr) return nullptr; + for (size_t i = 0; i < arr.size(); ++i) { + JLocal elem{env, MakeJObject(env, inst, arr[i])}; + env->SetObjectArrayElement(jarr, i, elem.obj()); + } + return jarr; +} + +static jobjectArray MakeJObject(JNIEnv *env, jobject inst, + llvm::ArrayRef arr) { + jobjectArray jarr = env->NewObjectArray(arr.size(), logMessageCls, nullptr); + if (!jarr) return nullptr; + for (size_t i = 0; i < arr.size(); ++i) { + JLocal elem{env, MakeJObject(env, inst, arr[i])}; + env->SetObjectArrayElement(jarr, i, elem.obj()); + } + return jarr; +} + +static jobjectArray MakeJObject(JNIEnv *env, jobject inst, + llvm::ArrayRef arr) { + jobjectArray jarr = env->NewObjectArray(arr.size(), rpcAnswerCls, nullptr); + if (!jarr) return nullptr; + for (size_t i = 0; i < arr.size(); ++i) { + JLocal elem{env, MakeJObject(env, inst, arr[i])}; + env->SetObjectArrayElement(jarr, i, elem.obj()); + } + return jarr; } +extern "C" { + /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: getType - * Signature: (Ljava/lang/String;)I + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getDefaultInstance + * Signature: ()I */ -JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getType - (JNIEnv *env, jclass, jstring key) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getDefaultInstance + (JNIEnv *, jclass) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return 0; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); - if (!val) return NT_UNASSIGNED; - return val->type(); + return nt::GetDefaultInstance(); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putBoolean - * Signature: (Ljava/lang/String;Z)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: createInstance + * Signature: ()I */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBoolean - (JNIEnv *env, jclass, jstring key, jboolean value) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_createInstance + (JNIEnv *, jclass) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - return nt::SetEntryValue(JStringRef{env, key}, - nt::Value::MakeBoolean(value != JNI_FALSE)); + return nt::CreateInstance(); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putDouble - * Signature: (Ljava/lang/String;D)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: destroyInstance + * Signature: (I)V */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDouble - (JNIEnv *env, jclass, jstring key, jdouble value) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyInstance + (JNIEnv *, jclass, jint inst) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - return nt::SetEntryValue(JStringRef{env, key}, - nt::Value::MakeDouble(value)); + nt::DestroyInstance(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putString - * Signature: (Ljava/lang/String;Ljava/lang/String;)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getInstanceFromHandle + * Signature: (I)I */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putString - (JNIEnv *env, jclass, jstring key, jstring value) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getInstanceFromHandle + (JNIEnv *, jclass, jint handle) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - if (!value) { - nullPointerEx.Throw(env, "value cannot be null"); - return false; - } - return nt::SetEntryValue(JStringRef{env, key}, - nt::Value::MakeString(JStringRef{env, value})); + return nt::GetInstanceFromHandle(handle); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putRaw - * Signature: (Ljava/lang/String;[B)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getEntry + * Signature: (ILjava/lang/String;)I */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putRaw__Ljava_lang_String_2_3B - (JNIEnv *env, jclass, jstring key, jbyteArray value) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntry + (JNIEnv *env, jclass, jint inst, jstring key) { if (!key) { nullPointerEx.Throw(env, "key cannot be null"); return false; } - if (!value) { - nullPointerEx.Throw(env, "value cannot be null"); - return false; - } - auto v = FromJavaRaw(env, value); - if (!v) return false; - return nt::SetEntryValue(JStringRef{env, key}, v); + return nt::GetEntry(inst, JStringRef{env, key}); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putRaw - * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getEntries + * Signature: (ILjava/lang/String;I)[I */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putRaw__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I - (JNIEnv *env, jclass, jstring key, jobject value, jint len) +JNIEXPORT jintArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntries + (JNIEnv *env, jclass, jint inst, jstring prefix, jint types) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - if (!value) { - nullPointerEx.Throw(env, "value cannot be null"); - return false; + if (!prefix) { + nullPointerEx.Throw(env, "prefix cannot be null"); + return nullptr; } - auto v = FromJavaRawBB(env, value, len); - if (!v) return false; - return nt::SetEntryValue(JStringRef{env, key}, v); + return MakeJIntArray(env, + nt::GetEntries(inst, JStringRef{env, prefix}, types)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putBooleanArray - * Signature: (Ljava/lang/String;[Z)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getEntryName + * Signature: (I)Ljava/lang/String; */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putBooleanArray - (JNIEnv *env, jclass, jstring key, jbooleanArray value) +JNIEXPORT jstring JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryName + (JNIEnv *env, jclass, jint entry) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - if (!value) { - nullPointerEx.Throw(env, "value cannot be null"); - return false; - } - auto v = FromJavaBooleanArray(env, value); - if (!v) return false; - return nt::SetEntryValue(JStringRef{env, key}, v); + return MakeJString(env, nt::GetEntryName(entry)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putDoubleArray - * Signature: (Ljava/lang/String;[D)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getEntryLastChange + * Signature: (I)J */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putDoubleArray - (JNIEnv *env, jclass, jstring key, jdoubleArray value) +JNIEXPORT jlong JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryLastChange + (JNIEnv *, jclass, jint entry) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - if (!value) { - nullPointerEx.Throw(env, "value cannot be null"); - return false; - } - auto v = FromJavaDoubleArray(env, value); - if (!v) return false; - return nt::SetEntryValue(JStringRef{env, key}, v); + return nt::GetEntryLastChange(entry); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: putStringArray - * Signature: (Ljava/lang/String;[Ljava/lang/String;)Z + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getType + * Signature: (I)I */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_putStringArray - (JNIEnv *env, jclass, jstring key, jobjectArray value) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getType + (JNIEnv *, jclass, jint entry) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - if (!value) { - nullPointerEx.Throw(env, "value cannot be null"); - return false; - } - auto v = FromJavaStringArray(env, value); - if (!v) return false; - return nt::SetEntryValue(JStringRef{env, key}, v); + return nt::GetEntryType(entry); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutBoolean - * Signature: (Ljava/lang/String;Z)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setBoolean + * Signature: (IJZZ)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBoolean - (JNIEnv *env, jclass, jstring key, jboolean value) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setBoolean + (JNIEnv *, jclass, jint entry, jlong time, jboolean value, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; + if (force) { + nt::SetEntryTypeValue(entry, + nt::Value::MakeBoolean(value != JNI_FALSE, time)); + return JNI_TRUE; } - nt::SetEntryTypeValue(JStringRef{env, key}, - nt::Value::MakeBoolean(value != JNI_FALSE)); + return nt::SetEntryValue(entry, + nt::Value::MakeBoolean(value != JNI_FALSE, time)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutDouble - * Signature: (Ljava/lang/String;D)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setDouble + * Signature: (IJDZ)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDouble - (JNIEnv *env, jclass, jstring key, jdouble value) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDouble + (JNIEnv *, jclass, jint entry, jlong time, jdouble value, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; + if (force) { + nt::SetEntryTypeValue(entry, nt::Value::MakeDouble(value, time)); + return JNI_TRUE; } - nt::SetEntryTypeValue(JStringRef{env, key}, nt::Value::MakeDouble(value)); + return nt::SetEntryValue(entry, nt::Value::MakeDouble(value, time)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutString - * Signature: (Ljava/lang/String;Ljava/lang/String;)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setString + * Signature: (IJLjava/lang/String;Z)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutString - (JNIEnv *env, jclass, jstring key, jstring value) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setString + (JNIEnv *env, jclass, jint entry, jlong time, jstring value, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } if (!value) { nullPointerEx.Throw(env, "value cannot be null"); - return; + return false; } - nt::SetEntryTypeValue(JStringRef{env, key}, - nt::Value::MakeString(JStringRef{env, value})); + if (force) { + nt::SetEntryTypeValue(entry, + nt::Value::MakeString(JStringRef{env, value}, time)); + return JNI_TRUE; + } + return nt::SetEntryValue(entry, + nt::Value::MakeString(JStringRef{env, value}, time)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutRaw - * Signature: (Ljava/lang/String;[B)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setRaw + * Signature: (IJ[BZ)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutRaw__Ljava_lang_String_2_3B - (JNIEnv *env, jclass, jstring key, jbyteArray value) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setRaw__IJ_3BZ + (JNIEnv *env, jclass, jint entry, jlong time, jbyteArray value, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } if (!value) { nullPointerEx.Throw(env, "value cannot be null"); - return; + return false; + } + auto v = FromJavaRaw(env, value, time); + if (!v) return false; + if (force) { + nt::SetEntryTypeValue(entry, v); + return JNI_TRUE; } - auto v = FromJavaRaw(env, value); - if (!v) return; - nt::SetEntryTypeValue(JStringRef{env, key}, v); + return nt::SetEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutRaw - * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setRaw + * Signature: (IJLjava/nio/ByteBuffer;IZ)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutRaw__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I - (JNIEnv *env, jclass, jstring key, jobject value, jint len) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setRaw__IJLjava_nio_ByteBuffer_2IZ + (JNIEnv *env, jclass, jint entry, jlong time, jobject value, jint len, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } if (!value) { nullPointerEx.Throw(env, "value cannot be null"); - return; + return false; + } + auto v = FromJavaRawBB(env, value, len, time); + if (!v) return false; + if (force) { + nt::SetEntryTypeValue(entry, v); + return JNI_TRUE; } - auto v = FromJavaRawBB(env, value, len); - if (!v) return; - nt::SetEntryTypeValue(JStringRef{env, key}, v); + return nt::SetEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutBooleanArray - * Signature: (Ljava/lang/String;[Z)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setBooleanArray + * Signature: (IJ[Z)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutBooleanArray - (JNIEnv *env, jclass, jstring key, jbooleanArray value) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setBooleanArray + (JNIEnv *env, jclass, jint entry, jlong time, jbooleanArray value, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } if (!value) { nullPointerEx.Throw(env, "value cannot be null"); - return; + return false; + } + auto v = FromJavaBooleanArray(env, value, time); + if (!v) return false; + if (force) { + nt::SetEntryTypeValue(entry, v); + return JNI_TRUE; } - auto v = FromJavaBooleanArray(env, value); - if (!v) return; - nt::SetEntryTypeValue(JStringRef{env, key}, v); + return nt::SetEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutDoubleArray - * Signature: (Ljava/lang/String;[D)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setDoubleArray + * Signature: (IJ[DZ)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutDoubleArray - (JNIEnv *env, jclass, jstring key, jdoubleArray value) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDoubleArray + (JNIEnv *env, jclass, jint entry, jlong time, jdoubleArray value, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } if (!value) { nullPointerEx.Throw(env, "value cannot be null"); - return; + return false; + } + auto v = FromJavaDoubleArray(env, value, time); + if (!v) return false; + if (force) { + nt::SetEntryTypeValue(entry, v); + return JNI_TRUE; } - auto v = FromJavaDoubleArray(env, value); - if (!v) return; - nt::SetEntryTypeValue(JStringRef{env, key}, v); + return nt::SetEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: forcePutStringArray - * Signature: (Ljava/lang/String;[Ljava/lang/String;)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setStringArray + * Signature: (IJ[Ljava/lang/String;Z)Z */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_forcePutStringArray - (JNIEnv *env, jclass, jstring key, jobjectArray value) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setStringArray + (JNIEnv *env, jclass, jint entry, jlong time, jobjectArray value, jboolean force) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } if (!value) { nullPointerEx.Throw(env, "value cannot be null"); - return; + return false; + } + auto v = FromJavaStringArray(env, value, time); + if (!v) return false; + if (force) { + nt::SetEntryTypeValue(entry, v); + return JNI_TRUE; } - auto v = FromJavaStringArray(env, value); - if (!v) return; - nt::SetEntryTypeValue(JStringRef{env, key}, v); + return nt::SetEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getValue - * Signature: (Ljava/lang/String;Ljava/lang/Object;)Ljava/lang/Object; + * Signature: (I)Ledu/wpi/first/networktables/NetworkTableValue; */ -JNIEXPORT jobject JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getValue - (JNIEnv *env, jclass, jstring key, jobject defaultValue) +JNIEXPORT jobject JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getValue + (JNIEnv *env, jclass, jint entry) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return nullptr; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); - if (!val) return defaultValue; - return MakeJObject(env, *val); + auto val = nt::GetEntryValue(entry); + return MakeJValue(env, val.get()); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getBoolean - * Signature: (Ljava/lang/String;Z)Z + * Signature: (IZ)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBoolean - (JNIEnv *env, jclass, jstring key, jboolean defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getBoolean + (JNIEnv *, jclass, jint entry, jboolean defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); + auto val = nt::GetEntryValue(entry); if (!val || !val->IsBoolean()) return defaultValue; return val->GetBoolean(); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getDouble - * Signature: (Ljava/lang/String;D)D + * Signature: (ID)D */ -JNIEXPORT jdouble JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDouble - (JNIEnv *env, jclass, jstring key, jdouble defaultValue) +JNIEXPORT jdouble JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getDouble + (JNIEnv *, jclass, jint entry, jdouble defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return 0; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); + auto val = nt::GetEntryValue(entry); if (!val || !val->IsDouble()) return defaultValue; return val->GetDouble(); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getString - * Signature: (Ljava/lang/String;Ljava/lang/String;)Ljava/lang/String; + * Signature: (ILjava/lang/String;)Ljava/lang/String; */ -JNIEXPORT jstring JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getString - (JNIEnv *env, jclass, jstring key, jstring defaultValue) +JNIEXPORT jstring JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getString + (JNIEnv *env, jclass, jint entry, jstring defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return nullptr; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); + auto val = nt::GetEntryValue(entry); if (!val || !val->IsString()) return defaultValue; return MakeJString(env, val->GetString()); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getRaw - * Signature: (Ljava/lang/String;[B)[B + * Signature: (I[B)[B */ -JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRaw - (JNIEnv *env, jclass, jstring key, jbyteArray defaultValue) +JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getRaw + (JNIEnv *env, jclass, jint entry, jbyteArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return nullptr; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); + auto val = nt::GetEntryValue(entry); if (!val || !val->IsRaw()) return defaultValue; return MakeJByteArray(env, val->GetRaw()); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getBooleanArray - * Signature: (Ljava/lang/String;[Z)[Z + * Signature: (I[Z)[Z */ -JNIEXPORT jbooleanArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getBooleanArray - (JNIEnv *env, jclass, jstring key, jbooleanArray defaultValue) +JNIEXPORT jbooleanArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getBooleanArray + (JNIEnv *env, jclass, jint entry, jbooleanArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return nullptr; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); + auto val = nt::GetEntryValue(entry); if (!val || !val->IsBooleanArray()) return defaultValue; return MakeJBooleanArray(env, val->GetBooleanArray()); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getDoubleArray - * Signature: (Ljava/lang/String;[D)[D + * Signature: (I[D)[D */ -JNIEXPORT jdoubleArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getDoubleArray - (JNIEnv *env, jclass, jstring key, jdoubleArray defaultValue) +JNIEXPORT jdoubleArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getDoubleArray + (JNIEnv *env, jclass, jint entry, jdoubleArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return nullptr; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); + auto val = nt::GetEntryValue(entry); if (!val || !val->IsDoubleArray()) return defaultValue; return MakeJDoubleArray(env, val->GetDoubleArray()); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getStringArray - * Signature: (Ljava/lang/String;[Ljava/lang/String;)[Ljava/lang/String; + * Signature: (I[Ljava/lang/String;)[Ljava/lang/String; */ -JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getStringArray - (JNIEnv *env, jclass, jstring key, jobjectArray defaultValue) +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getStringArray + (JNIEnv *env, jclass, jint entry, jobjectArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return nullptr; - } - auto val = nt::GetEntryValue(JStringRef{env, key}); + auto val = nt::GetEntryValue(entry); if (!val || !val->IsStringArray()) return defaultValue; return MakeJStringArray(env, val->GetStringArray()); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefaultBoolean - * Signature: (Ljava/lang/String;Z)Z + * Signature: (IJZ)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setDefaultBoolean - (JNIEnv *env, jclass, jstring key, jboolean defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultBoolean + (JNIEnv *, jclass, jint entry, jlong time, jboolean defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } - return nt::SetDefaultEntryValue(JStringRef{env, key}, - nt::Value::MakeBoolean(defaultValue != JNI_FALSE)); + return nt::SetDefaultEntryValue( + entry, nt::Value::MakeBoolean(defaultValue != JNI_FALSE, time)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefaultDouble - * Signature: (Ljava/lang/String;D)Z + * Signature: (IJD)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setDefaultDouble - (JNIEnv *env, jclass, jstring key, jdouble defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultDouble + (JNIEnv *, jclass, jint entry, jlong time, jdouble defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return 0; - } - return nt::SetDefaultEntryValue(JStringRef{env, key}, - nt::Value::MakeDouble(defaultValue)); + return nt::SetDefaultEntryValue(entry, + nt::Value::MakeDouble(defaultValue, time)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefaultString - * Signature: (Ljava/lang/String;Ljava/lang/String;)Z + * Signature: (IJLjava/lang/String;)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setDefaultString - (JNIEnv *env, jclass, jstring key, jstring defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultString + (JNIEnv *env, jclass, jint entry, jlong time, jstring defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } if (!defaultValue) { nullPointerEx.Throw(env, "defaultValue cannot be null"); return false; } return nt::SetDefaultEntryValue( - JStringRef{env, key}, - nt::Value::MakeString(JStringRef{env, defaultValue})); + entry, nt::Value::MakeString(JStringRef{env, defaultValue}, time)); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefaultRaw - * Signature: (Ljava/lang/String;[B)Z + * Signature: (IJ[B)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setDefaultRaw - (JNIEnv *env, jclass, jstring key, jbyteArray defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultRaw + (JNIEnv *env, jclass, jint entry, jlong time, jbyteArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } if (!defaultValue) { nullPointerEx.Throw(env, "defaultValue cannot be null"); return false; } - auto v = FromJavaRaw(env, defaultValue); - return nt::SetDefaultEntryValue(JStringRef{env, key}, v); + auto v = FromJavaRaw(env, defaultValue, time); + return nt::SetDefaultEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefaultBooleanArray - * Signature: (Ljava/lang/String;[Z)Z + * Signature: (IJ[Z)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setDefaultBooleanArray - (JNIEnv *env, jclass, jstring key, jbooleanArray defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultBooleanArray + (JNIEnv *env, jclass, jint entry, jlong time, jbooleanArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } if (!defaultValue) { nullPointerEx.Throw(env, "defaultValue cannot be null"); return false; } - auto v = FromJavaBooleanArray(env, defaultValue); - return nt::SetDefaultEntryValue(JStringRef{env, key}, v); + auto v = FromJavaBooleanArray(env, defaultValue, time); + return nt::SetDefaultEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefaultDoubleArray - * Signature: (Ljava/lang/String;[D)Z + * Signature: (IJ[D)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setDefaultDoubleArray - (JNIEnv *env, jclass, jstring key, jdoubleArray defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultDoubleArray + (JNIEnv *env, jclass, jint entry, jlong time, jdoubleArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } if (!defaultValue) { nullPointerEx.Throw(env, "defaultValue cannot be null"); return false; } - auto v = FromJavaDoubleArray(env, defaultValue); - return nt::SetDefaultEntryValue(JStringRef{env, key}, v); + auto v = FromJavaDoubleArray(env, defaultValue, time); + return nt::SetDefaultEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setDefaultStringArray - * Signature: (Ljava/lang/String;[Ljava/lang/String;)Z + * Signature: (IJ[Ljava/lang/String;)Z */ -JNIEXPORT jboolean JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setDefaultStringArray - (JNIEnv *env, jclass, jstring key, jobjectArray defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setDefaultStringArray + (JNIEnv *env, jclass, jint entry, jlong time, jobjectArray defaultValue) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return false; - } if (!defaultValue) { nullPointerEx.Throw(env, "defaultValue cannot be null"); return false; } - auto v = FromJavaStringArray(env, defaultValue); - return nt::SetDefaultEntryValue(JStringRef{env, key}, v); + auto v = FromJavaStringArray(env, defaultValue, time); + return nt::SetDefaultEntryValue(entry, v); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setEntryFlags - * Signature: (Ljava/lang/String;I)V + * Signature: (II)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setEntryFlags - (JNIEnv *env, jclass, jstring key, jint flags) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setEntryFlags + (JNIEnv *, jclass, jint entry, jint flags) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } - nt::SetEntryFlags(JStringRef{env, key}, flags); + nt::SetEntryFlags(entry, flags); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getEntryFlags - * Signature: (Ljava/lang/String;)I + * Signature: (I)I */ -JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getEntryFlags - (JNIEnv *env, jclass, jstring key) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryFlags + (JNIEnv *, jclass, jint entry) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return 0; - } - return nt::GetEntryFlags(JStringRef{env, key}); + return nt::GetEntryFlags(entry); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: deleteEntry - * Signature: (Ljava/lang/String;)V + * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteEntry - (JNIEnv *env, jclass, jstring key) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_deleteEntry + (JNIEnv *, jclass, jint entry) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return; - } - nt::DeleteEntry(JStringRef{env, key}); + nt::DeleteEntry(entry); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: deleteAllEntries - * Signature: ()V + * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_deleteAllEntries - (JNIEnv *, jclass) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_deleteAllEntries + (JNIEnv *, jclass, jint inst) { - nt::DeleteAllEntries(); + nt::DeleteAllEntries(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: getEntries - * Signature: (Ljava/lang/String;I)[Ledu/wpi/first/wpilibj/networktables/EntryInfo; + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getEntryInfoHandle + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;I)Ledu/wpi/first/networktables/EntryInfo; + */ +JNIEXPORT jobject JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryInfoHandle + (JNIEnv *env, jclass, jobject inst, jint entry) +{ + return MakeJObject(env, inst, nt::GetEntryInfo(entry)); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getEntryInfo + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;ILjava/lang/String;I)[Ledu/wpi/first/networktables/EntryInfo; */ -JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getEntries - (JNIEnv *env, jclass, jstring prefix, jint types) +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getEntryInfo + (JNIEnv *env, jclass, jobject instObject, jint inst, jstring prefix, jint types) { if (!prefix) { nullPointerEx.Throw(env, "prefix cannot be null"); return nullptr; } - auto arr = nt::GetEntryInfo(JStringRef{env, prefix}, types); + auto arr = nt::GetEntryInfo(inst, JStringRef{env, prefix}, types); jobjectArray jarr = env->NewObjectArray(arr.size(), entryInfoCls, nullptr); if (!jarr) return nullptr; for (size_t i = 0; i < arr.size(); ++i) { - JLocal jelem{env, MakeJObject(env, arr[i])}; + JLocal jelem{env, MakeJObject(env, instObject, arr[i])}; env->SetObjectArrayElement(jarr, i, jelem); } return jarr; } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: flush - * Signature: ()V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: createEntryListenerPoller + * Signature: (I)I */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_flush - (JNIEnv *, jclass) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_createEntryListenerPoller + (JNIEnv *, jclass, jint inst) { - nt::Flush(); + return nt::CreateEntryListenerPoller(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: addEntryListener - * Signature: (Ljava/lang/String;Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/EntryListenerFunction;Z)I + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: destroyEntryListenerPoller + * Signature: (I)V */ -JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addEntryListener - (JNIEnv *envouter, jclass, jstring prefix, jobject listener, jint flags) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyEntryListenerPoller + (JNIEnv *, jclass, jint poller) +{ + nt::DestroyEntryListenerPoller(poller); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: addPolledEntryListener + * Signature: (ILjava/lang/String;I)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledEntryListener__ILjava_lang_String_2I + (JNIEnv *env, jclass, jint poller, jstring prefix, jint flags) { if (!prefix) { - nullPointerEx.Throw(envouter, "prefix cannot be null"); + nullPointerEx.Throw(env, "prefix cannot be null"); return 0; } - if (!listener) { - nullPointerEx.Throw(envouter, "listener cannot be null"); - return 0; + return nt::AddPolledEntryListener(poller, JStringRef{env, prefix}, flags); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: addPolledEntryListener + * Signature: (II)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledEntryListener__III + (JNIEnv *env, jclass, jint poller, jint entry, jint flags) +{ + return nt::AddPolledEntryListener(poller, entry, flags); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollEntryListener + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;I)[Ledu/wpi/first/networktables/EntryNotification; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollEntryListener + (JNIEnv *env, jclass, jobject inst, jint poller) +{ + auto events = nt::PollEntryListener(poller); + if (events.empty()) { + interruptedEx.Throw(env, "PollEntryListener interrupted"); + return nullptr; + } + return MakeJObject(env, inst, events); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollEntryListenerTimeout + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;ID)[Ledu/wpi/first/networktables/EntryNotification; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollEntryListenerTimeout + (JNIEnv *env, jclass, jobject inst, jint poller, jdouble timeout) +{ + bool timed_out = false; + auto events = nt::PollEntryListener(poller, timeout, &timed_out); + if (events.empty() && !timed_out) { + interruptedEx.Throw(env, "PollEntryListener interrupted"); + return nullptr; } + return MakeJObject(env, inst, events); +} - // the shared pointer to the weak global will keep it around until the - // entry listener is destroyed - auto listener_global = - std::make_shared>(envouter, listener); - - // cls is a temporary here; cannot be used within callback functor - jclass cls = envouter->GetObjectClass(listener); - if (!cls) return 0; - - // method ids, on the other hand, are safe to retain - jmethodID mid = envouter->GetMethodID( - cls, "apply", "(ILjava/lang/String;Ljava/lang/Object;I)V"); - if (!mid) return 0; - - return nt::AddEntryListener( - JStringRef{envouter, prefix}, - [=](unsigned int uid, nt::StringRef name, - std::shared_ptr value, unsigned int flags_) { - JNIEnv *env = listenerEnv; - if (!env || !env->functions) return; - - // get the handler - auto handler = listener_global->obj(); - - // convert the value into the appropriate Java type - JLocal jobj{env, MakeJObject(env, *value)}; - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - if (!jobj) return; - - JLocal jname{env, MakeJString(env, name)}; - env->CallVoidMethod(handler, mid, (jint)uid, jname.obj(), jobj.obj(), - (jint)(flags_)); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - }, - flags); -} - -/* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: cancelPollEntryListener + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollEntryListener + (JNIEnv *, jclass, jint poller) +{ + nt::CancelPollEntryListener(poller); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: removeEntryListener * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeEntryListener +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_removeEntryListener (JNIEnv *, jclass, jint entryListenerUid) { nt::RemoveEntryListener(entryListenerUid); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: addConnectionListener - * Signature: (Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/ConnectionListenerFunction;Z)I + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: waitForEntryListenerQueue + * Signature: (ID)Z */ -JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_addConnectionListener - (JNIEnv *envouter, jclass, jobject listener, jboolean immediateNotify) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForEntryListenerQueue + (JNIEnv *, jclass, jint inst, jdouble timeout) { - if (!listener) { - nullPointerEx.Throw(envouter, "listener cannot be null"); - return 0; + return nt::WaitForEntryListenerQueue(inst, timeout); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: createConnectionListenerPoller + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_createConnectionListenerPoller + (JNIEnv *, jclass, jint inst) +{ + return nt::CreateConnectionListenerPoller(inst); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: destroyConnectionListenerPoller + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyConnectionListenerPoller + (JNIEnv *, jclass, jint poller) +{ + nt::DestroyConnectionListenerPoller(poller); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: addPolledConnectionListener + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledConnectionListener + (JNIEnv *env, jclass, jint poller, jboolean immediateNotify) +{ + return nt::AddPolledConnectionListener(poller, immediateNotify); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollConnectionListener + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;I)[Ledu/wpi/first/networktables/ConnectionNotification; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollConnectionListener + (JNIEnv *env, jclass, jobject inst, jint poller) +{ + auto events = nt::PollConnectionListener(poller); + if (events.empty()) { + interruptedEx.Throw(env, "PollConnectionListener interrupted"); + return nullptr; + } + return MakeJObject(env, inst, events); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollConnectionListenerTimeout + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;ID)[Ledu/wpi/first/networktables/ConnectionNotification; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollConnectionListenerTimeout + (JNIEnv *env, jclass, jobject inst, jint poller, jdouble timeout) +{ + bool timed_out = false; + auto events = nt::PollConnectionListener(poller, timeout, &timed_out); + if (events.empty() && !timed_out) { + interruptedEx.Throw(env, "PollConnectionListener interrupted"); + return nullptr; } + return MakeJObject(env, inst, events); +} - // the shared pointer to the weak global will keep it around until the - // entry listener is destroyed - auto listener_global = - std::make_shared>(envouter, listener); - - // cls is a temporary here; cannot be used within callback functor - jclass cls = envouter->GetObjectClass(listener); - if (!cls) return 0; - - // method ids, on the other hand, are safe to retain - jmethodID mid = envouter->GetMethodID( - cls, "apply", "(IZLedu/wpi/first/wpilibj/networktables/ConnectionInfo;)V"); - if (!mid) return 0; - - return nt::AddConnectionListener( - [=](unsigned int uid, bool connected, const nt::ConnectionInfo& conn) { - JNIEnv *env = listenerEnv; - if (!env || !env->functions) return; - - // get the handler - auto handler = listener_global->obj(); - //if (!handler) goto done; // can happen due to weak reference - - // convert into the appropriate Java type - JLocal jobj{env, MakeJObject(env, conn)}; - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - return; - } - if (!jobj) return; - - env->CallVoidMethod(handler, mid, (jint)uid, - (jboolean)(connected ? 1 : 0), jobj.obj()); - if (env->ExceptionCheck()) { - env->ExceptionDescribe(); - env->ExceptionClear(); - } - }, - immediateNotify != JNI_FALSE); -} - -/* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: cancelPollConnectionListener + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollConnectionListener + (JNIEnv *, jclass, jint poller) +{ + nt::CancelPollConnectionListener(poller); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: removeConnectionListener * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_removeConnectionListener +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_removeConnectionListener (JNIEnv *, jclass, jint connListenerUid) { nt::RemoveConnectionListener(connListenerUid); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: getRpc - * Signature: (Ljava/lang/String;[B)[B + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: waitForConnectionListenerQueue + * Signature: (ID)Z */ -JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getRpc - (JNIEnv *env, jclass, jstring key, jbyteArray defaultValue) +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForConnectionListenerQueue + (JNIEnv *, jclass, jint inst, jdouble timeout) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return nullptr; + return nt::WaitForConnectionListenerQueue(inst, timeout); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: createRpcCallPoller + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_createRpcCallPoller + (JNIEnv *, jclass, jint inst) +{ + return nt::CreateRpcCallPoller(inst); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: destroyRpcCallPoller + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyRpcCallPoller + (JNIEnv *, jclass, jint poller) +{ + nt::DestroyRpcCallPoller(poller); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: createPolledRpc + * Signature: (I[BI)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_createPolledRpc + (JNIEnv *env, jclass, jint entry, jbyteArray def, jint poller) +{ + if (!def) { + nullPointerEx.Throw(env, "def cannot be null"); + return; } - if (!defaultValue) { - nullPointerEx.Throw(env, "defaultValue cannot be null"); + nt::CreatePolledRpc(entry, JByteArrayRef{env, def}, poller); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollRpc + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;I)[Ledu/wpi/first/networktables/RpcAnswer; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollRpc + (JNIEnv *env, jclass, jobject inst, jint poller) +{ + auto infos = nt::PollRpc(poller); + if (infos.empty()) { + interruptedEx.Throw(env, "PollRpc interrupted"); return nullptr; } - auto val = nt::GetEntryValue(JStringRef{env, key}); - if (!val || !val->IsRpc()) return defaultValue; - return MakeJByteArray(env, val->GetRpc()); + return MakeJObject(env, inst, infos); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: callRpc - * Signature: (Ljava/lang/String;[B)I + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollRpcTimeout + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;ID)[Ledu/wpi/first/networktables/RpcAnswer; */ -JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc__Ljava_lang_String_2_3B - (JNIEnv *env, jclass, jstring key, jbyteArray params) +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollRpcTimeout + (JNIEnv *env, jclass, jobject inst, jint poller, jdouble timeout) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return 0; + bool timed_out = false; + auto infos = nt::PollRpc(poller, timeout, &timed_out); + if (infos.empty() && !timed_out) { + interruptedEx.Throw(env, "PollRpc interrupted"); + return nullptr; } - if (!params) { - nullPointerEx.Throw(env, "params cannot be null"); - return 0; + return MakeJObject(env, inst, infos); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: cancelPollRpc + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollRpc + (JNIEnv *, jclass, jint poller) +{ + nt::CancelPollRpc(poller); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: waitForRpcCallQueue + * Signature: (ID)Z + */ +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForRpcCallQueue + (JNIEnv *, jclass, jint inst, jdouble timeout) +{ + return nt::WaitForRpcCallQueue(inst, timeout); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: postRpcResponse + * Signature: (II[B)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_postRpcResponse + (JNIEnv *env, jclass, jint entry, jint call, jbyteArray result) +{ + if (!result) { + nullPointerEx.Throw(env, "result cannot be null"); + return; } - return nt::CallRpc(JStringRef{env, key}, JByteArrayRef{env, params}); + nt::PostRpcResponse(entry, call, JByteArrayRef{env, result}); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: callRpc - * Signature: (Ljava/lang/String;Ljava/nio/ByteBuffer;I)I + * Signature: (I[B)I */ -JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_callRpc__Ljava_lang_String_2Ljava_nio_ByteBuffer_2I - (JNIEnv *env, jclass, jstring key, jobject params, jint params_len) +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_callRpc + (JNIEnv *env, jclass, jint entry, jbyteArray params) { - if (!key) { - nullPointerEx.Throw(env, "key cannot be null"); - return 0; - } if (!params) { nullPointerEx.Throw(env, "params cannot be null"); return 0; } - return nt::CallRpc(JStringRef{env, key}, - JByteArrayRef{env, params, params_len}); + return nt::CallRpc(entry, JByteArrayRef{env, params}); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getRpcResult + * Signature: (II)[B + */ +JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getRpcResult__II + (JNIEnv *env, jclass, jint entry, jint call) +{ + std::string result; + if (!nt::GetRpcResult(entry, call, &result)) return nullptr; + return MakeJByteArray(env, result); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getRpcResult + * Signature: (IID)[B + */ +JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getRpcResult__IID + (JNIEnv *env, jclass, jint entry, jint call, jdouble timeout) +{ + std::string result; + bool timed_out = false; + if (!nt::GetRpcResult(entry, call, &result, timeout, &timed_out)) + return nullptr; + return MakeJByteArray(env, result); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: cancelRpcResult + * Signature: (II)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelRpcResult + (JNIEnv *, jclass, jint entry, jint call) +{ + nt::CancelRpcResult(entry, call); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getRpc + * Signature: (I[B)[B + */ +JNIEXPORT jbyteArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getRpc + (JNIEnv *env, jclass, jint entry, jbyteArray defaultValue) +{ + auto val = nt::GetEntryValue(entry); + if (!val || !val->IsRpc()) return defaultValue; + return MakeJByteArray(env, val->GetRpc()); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setNetworkIdentity - * Signature: (Ljava/lang/String;)V + * Signature: (ILjava/lang/String;)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setNetworkIdentity - (JNIEnv *env, jclass, jstring name) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setNetworkIdentity + (JNIEnv *env, jclass, jint inst, jstring name) { if (!name) { nullPointerEx.Throw(env, "name cannot be null"); return; } - nt::SetNetworkIdentity(JStringRef{env, name}); + nt::SetNetworkIdentity(inst, JStringRef{env, name}); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: getNetworkMode + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getNetworkMode + (JNIEnv *, jclass, jint inst) +{ + return nt::GetNetworkMode(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: startServer - * Signature: (Ljava/lang/String;Ljava/lang/String;I)V + * Signature: (ILjava/lang/String;Ljava/lang/String;I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startServer - (JNIEnv *env, jclass, jstring persistFilename, jstring listenAddress, - jint port) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_startServer + (JNIEnv *env, jclass, jint inst, jstring persistFilename, jstring listenAddress, jint port) { if (!persistFilename) { nullPointerEx.Throw(env, "persistFilename cannot be null"); @@ -1220,54 +1310,54 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI nullPointerEx.Throw(env, "listenAddress cannot be null"); return; } - nt::StartServer(JStringRef{env, persistFilename}, + nt::StartServer(inst, JStringRef{env, persistFilename}, JStringRef{env, listenAddress}.c_str(), port); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: stopServer - * Signature: ()V + * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopServer - (JNIEnv *, jclass) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_stopServer + (JNIEnv *, jclass, jint inst) { - nt::StopServer(); + nt::StopServer(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: startClient - * Signature: ()V + * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient__ - (JNIEnv *env, jclass) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient__I + (JNIEnv *, jclass, jint inst) { - nt::StartClient(); + nt::StartClient(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: startClient - * Signature: (Ljava/lang/String;I)V + * Signature: (ILjava/lang/String;I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient__Ljava_lang_String_2I - (JNIEnv *env, jclass, jstring serverName, jint port) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient__ILjava_lang_String_2I + (JNIEnv *env, jclass, jint inst, jstring serverName, jint port) { if (!serverName) { nullPointerEx.Throw(env, "serverName cannot be null"); return; } - nt::StartClient(JStringRef{env, serverName}.c_str(), port); + nt::StartClient(inst, JStringRef{env, serverName}.c_str(), port); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: startClient - * Signature: ([Ljava/lang/String;[I)V + * Signature: (I[Ljava/lang/String;[I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startClient___3Ljava_lang_String_2_3I - (JNIEnv *env, jclass, jobjectArray serverNames, jintArray ports) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_startClient__I_3Ljava_lang_String_2_3I + (JNIEnv *env, jclass, jint inst, jobjectArray serverNames, jintArray ports) { if (!serverNames) { nullPointerEx.Throw(env, "serverNames cannot be null"); @@ -1302,53 +1392,53 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI portInts[i])); } env->ReleaseIntArrayElements(ports, portInts, JNI_ABORT); - nt::StartClient(servers); + nt::StartClient(inst, servers); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: stopClient - * Signature: ()V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: startClientTeam + * Signature: (III)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopClient - (JNIEnv *, jclass) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_startClientTeam + (JNIEnv *env, jclass cls, jint inst, jint team, jint port) { - nt::StopClient(); + nt::StartClientTeam(inst, team, port); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: getNetworkMode - * Signature: () + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: stopClient + * Signature: (I)V */ -JNIEXPORT jint JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getNetworkMode - (JNIEnv *, jclass) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_stopClient + (JNIEnv *, jclass, jint inst) { - return nt::GetNetworkMode(); + nt::StopClient(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setServer - * Signature: (Ljava/lang/String;I)V + * Signature: (ILjava/lang/String;I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setServer__Ljava_lang_String_2I - (JNIEnv *env, jclass, jstring serverName, jint port) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setServer__ILjava_lang_String_2I + (JNIEnv *env, jclass, jint inst, jstring serverName, jint port) { if (!serverName) { nullPointerEx.Throw(env, "serverName cannot be null"); return; } - nt::SetServer(JStringRef{env, serverName}.c_str(), port); + nt::SetServer(inst, JStringRef{env, serverName}.c_str(), port); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setServer - * Signature: ([Ljava/lang/String;[I)V + * Signature: (I[Ljava/lang/String;[I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setServer___3Ljava_lang_String_2_3I - (JNIEnv *env, jclass, jobjectArray serverNames, jintArray ports) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setServer__I_3Ljava_lang_String_2_3I + (JNIEnv *env, jclass, jint inst, jobjectArray serverNames, jintArray ports) { if (!serverNames) { nullPointerEx.Throw(env, "serverNames cannot be null"); @@ -1383,51 +1473,73 @@ JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI portInts[i])); } env->ReleaseIntArrayElements(ports, portInts, JNI_ABORT); - nt::SetServer(servers); + nt::SetServer(inst, servers); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: setServerTeam + * Signature: (III)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setServerTeam + (JNIEnv *env, jclass, jint inst, jint team, jint port) +{ + nt::SetServerTeam(inst, team, port); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: startDSClient - * Signature: (I)V + * Signature: (II)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_startDSClient - (JNIEnv *env, jclass, jint port) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_startDSClient + (JNIEnv *, jclass, jint inst, jint port) { - nt::StartDSClient(port); + nt::StartDSClient(inst, port); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: stopDSClient * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_stopDSClient - (JNIEnv *env, jclass) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_stopDSClient + (JNIEnv *, jclass, jint inst) { - nt::StopDSClient(); + nt::StopDSClient(inst); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: setUpdateRate - * Signature: (D)V + * Signature: (ID)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setUpdateRate - (JNIEnv *, jclass, jdouble interval) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_setUpdateRate + (JNIEnv *, jclass, jint inst, jdouble interval) { - nt::SetUpdateRate(interval); + nt::SetUpdateRate(inst, interval); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: flush + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_flush + (JNIEnv *, jclass, jint inst) +{ + nt::Flush(inst); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: getConnections - * Signature: ()[Ledu/wpi/first/wpilibj/networktables/ConnectionInfo; + * Signature: (I)[Ledu/wpi/first/networktables/ConnectionInfo; */ -JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_getConnections - (JNIEnv *env, jclass) +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_getConnections + (JNIEnv *env, jclass, jint inst) { - auto arr = nt::GetConnections(); + auto arr = nt::GetConnections(inst); jobjectArray jarr = env->NewObjectArray(arr.size(), connectionInfoCls, nullptr); if (!jarr) return nullptr; @@ -1439,39 +1551,51 @@ JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkT } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: isConnected + * Signature: (I)Z + */ +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_isConnected + (JNIEnv *, jclass, jint inst) +{ + return nt::IsConnected(inst); +} + +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: savePersistent - * Signature: (Ljava/lang/String;)V + * Signature: (ILjava/lang/String;)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_savePersistent - (JNIEnv *env, jclass, jstring filename) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_savePersistent + (JNIEnv *env, jclass, jint inst, jstring filename) { if (!filename) { nullPointerEx.Throw(env, "filename cannot be null"); return; } - const char *err = nt::SavePersistent(JStringRef{env, filename}); + const char *err = nt::SavePersistent(inst, JStringRef{env, filename}); if (err) persistentEx.Throw(env, err); } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: loadPersistent - * Signature: (Ljava/lang/String;)[Ljava/lang/String; + * Signature: (ILjava/lang/String;)[Ljava/lang/String; */ -JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_loadPersistent - (JNIEnv *env, jclass, jstring filename) +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_loadPersistent + (JNIEnv *env, jclass, jint inst, jstring filename) { if (!filename) { nullPointerEx.Throw(env, "filename cannot be null"); return nullptr; } std::vector warns; - const char *err = nt::LoadPersistent(JStringRef{env, filename}, - [&](size_t line, const char *msg) { - std::ostringstream oss; + const char* err = nt::LoadPersistent(inst, JStringRef{env, filename}, + [&](size_t line, const char* msg) { + llvm::SmallString<128> warn; + llvm::raw_svector_ostream oss(warn); oss << line << ": " << msg; - warns.push_back(oss.str()); + warns.emplace_back(oss.str()); }); if (err) { persistentEx.Throw(env, err); @@ -1481,83 +1605,113 @@ JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkT } /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI + * Class: edu_wpi_first_networktables_NetworkTablesJNI * Method: now * Signature: ()J */ -JNIEXPORT jlong JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_now +JNIEXPORT jlong JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_now (JNIEnv *, jclass) { return nt::Now(); } -} // extern "C" +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: createLoggerPoller + * Signature: (I)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_createLoggerPoller + (JNIEnv *, jclass, jint inst) +{ + return nt::CreateLoggerPoller(inst); +} -namespace { +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: destroyLoggerPoller + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_destroyLoggerPoller + (JNIEnv *, jclass, jint poller) +{ + nt::DestroyLoggerPoller(poller); +} -struct LogMessage { - public: - LogMessage(unsigned int level, const char *file, unsigned int line, - const char *msg) - : m_level(level), m_file(file), m_line(line), m_msg(msg) {} +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: addPolledLogger + * Signature: (III)I + */ +JNIEXPORT jint JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_addPolledLogger + (JNIEnv *, jclass, jint poller, jint minLevel, jint maxLevel) +{ + return nt::AddPolledLogger(poller, minLevel, maxLevel); +} - void CallJava(JNIEnv* env, jobject func, jmethodID mid) { - JLocal file{env, MakeJString(env, m_file)}; - JLocal msg{env, MakeJString(env, m_msg)}; - env->CallVoidMethod(func, mid, (jint)m_level, file.obj(), - (jint)m_line, msg.obj()); +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollLogger + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;I)[Ledu/wpi/first/networktables/LogMessage; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollLogger + (JNIEnv *env, jclass, jobject inst, jint poller) +{ + auto events = nt::PollLogger(poller); + if (events.empty()) { + interruptedEx.Throw(env, "PollLogger interrupted"); + return nullptr; } + return MakeJObject(env, inst, events); +} - static const char* GetName() { return "NTLogger"; } - static JavaVM* GetJVM() { return jvm; } - - private: - unsigned int m_level; - const char* m_file; - unsigned int m_line; - std::string m_msg; -}; - -typedef JSingletonCallbackManager LoggerJNI; - -} // anonymous namespace - -ATOMIC_STATIC_INIT(LoggerJNI) +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: pollLogger + * Signature: (Ledu/wpi/first/networktables/NetworkTableInstance;ID)[Ledu/wpi/first/networktables/LogMessage; + */ +JNIEXPORT jobjectArray JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_pollLoggerTimeout + (JNIEnv *env, jclass, jobject inst, jint poller, jdouble timeout) +{ + bool timed_out = false; + auto events = nt::PollLogger(poller, timeout, &timed_out); + if (events.empty() && !timed_out) { + interruptedEx.Throw(env, "PollLogger interrupted"); + return nullptr; + } + return MakeJObject(env, inst, events); +} -extern "C" { +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: cancelPollLogger + * Signature: (I)V + */ +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_cancelPollLogger + (JNIEnv *, jclass, jint poller) +{ + nt::CancelPollLogger(poller); +} /* - * Class: edu_wpi_first_wpilibj_networktables_NetworkTablesJNI - * Method: setLogger - * Signature: (Ledu/wpi/first/wpilibj/networktables/NetworkTablesJNI/LoggerFunction;I)V + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: removeLogger + * Signature: (I)V */ -JNIEXPORT void JNICALL Java_edu_wpi_first_wpilibj_networktables_NetworkTablesJNI_setLogger - (JNIEnv *env, jclass, jobject func, jint minLevel) +JNIEXPORT void JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_removeLogger + (JNIEnv *, jclass, jint logger) { - if (!func) { - nullPointerEx.Throw(env, "func cannot be null"); - return; - } + nt::RemoveLogger(logger); +} - // cls is a temporary here; cannot be used within callback functor - jclass cls = env->GetObjectClass(func); - if (!cls) return; - - // method ids, on the other hand, are safe to retain - jmethodID mid = env->GetMethodID( - cls, "apply", "(ILjava/lang/String;ILjava/lang/String;)V"); - if (!mid) return; - - auto& logger = LoggerJNI::GetInstance(); - logger.Start(); - logger.SetFunc(env, func, mid); - - nt::SetLogger( - [](unsigned int level, const char *file, unsigned int line, - const char *msg) { - LoggerJNI::GetInstance().Send(level, file, line, msg); - }, - minLevel); +/* + * Class: edu_wpi_first_networktables_NetworkTablesJNI + * Method: waitForLoggerQueue + * Signature: (ID)Z + */ +JNIEXPORT jboolean JNICALL Java_edu_wpi_first_networktables_NetworkTablesJNI_waitForLoggerQueue + (JNIEnv *, jclass, jint inst, jdouble timeout) +{ + return nt::WaitForLoggerQueue(inst, timeout); } } // extern "C" diff --git a/src/main/native/cpp/networktables/NetworkTable.cpp b/src/main/native/cpp/networktables/NetworkTable.cpp index 912143a..3e8cfd5 100644 --- a/src/main/native/cpp/networktables/NetworkTable.cpp +++ b/src/main/native/cpp/networktables/NetworkTable.cpp @@ -2,14 +2,18 @@ #include -#include "llvm/raw_ostream.h" #include "llvm/SmallString.h" #include "llvm/StringMap.h" -#include "tables/ITableListener.h" +#include "llvm/raw_ostream.h" +#include "networktables/NetworkTableInstance.h" #include "ntcore.h" +#include "tables/ITableListener.h" -using llvm::StringRef; -using nt::NetworkTable; +using namespace nt; + +#ifdef __GNUC__ +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif const char NetworkTable::PATH_SEPARATOR_CHAR = '/'; std::string NetworkTable::s_persistent_filename = "networktables.ini"; @@ -20,21 +24,23 @@ unsigned int NetworkTable::s_port = NT_DEFAULT_PORT; void NetworkTable::Initialize() { if (s_running) Shutdown(); + auto inst = NetworkTableInstance::GetDefault(); if (s_client) { - nt::StartClient(); - if (s_enable_ds) nt::StartDSClient(s_port); + inst.StartClient(); + if (s_enable_ds) inst.StartDSClient(s_port); } else - nt::StartServer(s_persistent_filename, "", s_port); + inst.StartServer(s_persistent_filename, "", s_port); s_running = true; } void NetworkTable::Shutdown() { if (!s_running) return; + auto inst = NetworkTableInstance::GetDefault(); if (s_client) { - nt::StopDSClient(); - nt::StopClient(); + inst.StopDSClient(); + inst.StopClient(); } else - nt::StopServer(); + inst.StopServer(); s_running = false; } @@ -43,80 +49,46 @@ void NetworkTable::SetClientMode() { s_client = true; } void NetworkTable::SetServerMode() { s_client = false; } void NetworkTable::SetTeam(int team) { - std::pair servers[5]; - - // 10.te.am.2 - llvm::SmallString<32> fixed; - { - llvm::raw_svector_ostream oss{fixed}; - oss << "10." << static_cast(team / 100) << '.' - << static_cast(team % 100) << ".2"; - servers[0] = std::make_pair(oss.str(), s_port); - } - - // 172.22.11.2 - servers[1] = std::make_pair("172.22.11.2", s_port); - - // roboRIO--FRC.local - llvm::SmallString<32> mdns; - { - llvm::raw_svector_ostream oss{mdns}; - oss << "roboRIO-" << team << "-FRC.local"; - servers[2] = std::make_pair(oss.str(), s_port); - } - - // roboRIO--FRC.lan - llvm::SmallString<32> mdns_lan; - { - llvm::raw_svector_ostream oss{mdns_lan}; - oss << "roboRIO-" << team << "-FRC.lan"; - servers[3] = std::make_pair(oss.str(), s_port); - } - - // roboRIO--FRC.frc-field.local - llvm::SmallString<64> field_local; - { - llvm::raw_svector_ostream oss{field_local}; - oss << "roboRIO-" << team << "-FRC.frc-field.local"; - servers[4] = std::make_pair(oss.str(), s_port); - } - - nt::SetServer(servers); + auto inst = NetworkTableInstance::GetDefault(); + inst.SetServerTeam(team, s_port); + if (s_enable_ds) inst.StartDSClient(s_port); } void NetworkTable::SetIPAddress(StringRef address) { + auto inst = NetworkTableInstance::GetDefault(); llvm::SmallString<32> addr_copy{address}; - nt::SetServer(addr_copy.c_str(), s_port); + inst.SetServer(addr_copy.c_str(), s_port); // Stop the DS client if we're explicitly connecting to localhost if (address == "localhost" || address == "127.0.0.1") - nt::StopDSClient(); + inst.StopDSClient(); else if (s_enable_ds) - nt::StartDSClient(s_port); + inst.StartDSClient(s_port); } void NetworkTable::SetIPAddress(llvm::ArrayRef addresses) { - llvm::SmallVector, 8> servers; - for (const auto& ip_address : addresses) - servers.emplace_back(std::make_pair(ip_address, s_port)); - nt::SetServer(servers); + auto inst = NetworkTableInstance::GetDefault(); + llvm::SmallVector servers; + for (const auto& ip_address : addresses) servers.emplace_back(ip_address); + inst.SetServer(servers, s_port); // Stop the DS client if we're explicitly connecting to localhost if (!addresses.empty() && (addresses[0] == "localhost" || addresses[0] == "127.0.0.1")) - nt::StopDSClient(); + inst.StopDSClient(); else if (s_enable_ds) - nt::StartDSClient(s_port); + inst.StartDSClient(s_port); } void NetworkTable::SetPort(unsigned int port) { s_port = port; } void NetworkTable::SetDSClientEnabled(bool enabled) { + auto inst = NetworkTableInstance::GetDefault(); s_enable_ds = enabled; if (s_enable_ds) - nt::StartDSClient(s_port); + inst.StartDSClient(s_port); else - nt::StopDSClient(); + inst.StopDSClient(); } void NetworkTable::SetPersistentFilename(StringRef filename) { @@ -124,44 +96,89 @@ void NetworkTable::SetPersistentFilename(StringRef filename) { } void NetworkTable::SetNetworkIdentity(StringRef name) { - nt::SetNetworkIdentity(name); + NetworkTableInstance::GetDefault().SetNetworkIdentity(name); } -void NetworkTable::GlobalDeleteAll() { nt::DeleteAllEntries(); } +void NetworkTable::GlobalDeleteAll() { + NetworkTableInstance::GetDefault().DeleteAllEntries(); +} -void NetworkTable::Flush() { nt::Flush(); } +void NetworkTable::Flush() { NetworkTableInstance::GetDefault().Flush(); } void NetworkTable::SetUpdateRate(double interval) { - nt::SetUpdateRate(interval); + NetworkTableInstance::GetDefault().SetUpdateRate(interval); } const char* NetworkTable::SavePersistent(llvm::StringRef filename) { - return nt::SavePersistent(filename); + return NetworkTableInstance::GetDefault().SavePersistent(filename); } const char* NetworkTable::LoadPersistent( llvm::StringRef filename, std::function warn) { - return nt::LoadPersistent(filename, warn); + return NetworkTableInstance::GetDefault().LoadPersistent(filename, warn); } std::shared_ptr NetworkTable::GetTable(StringRef key) { if (!s_running) Initialize(); - if (key.empty() || key[0] == PATH_SEPARATOR_CHAR) { - return std::make_shared(key, private_init()); - } else { - llvm::SmallString<128> path; + return NetworkTableInstance::GetDefault().GetTable(key); +} + +NetworkTable::NetworkTable(NT_Inst inst, StringRef path) + : m_inst(inst), m_path(path) {} + +NetworkTable::~NetworkTable() { + for (auto& i : m_listeners) RemoveEntryListener(i.second); +} + +NetworkTableInstance NetworkTable::GetInstance() const { + return NetworkTableInstance{m_inst}; +} + +NetworkTableEntry NetworkTable::GetEntry(llvm::StringRef key) const { + std::lock_guard lock(m_mutex); + NT_Entry& entry = m_entries[key]; + if (entry == 0) { + llvm::SmallString<128> path(m_path); path += PATH_SEPARATOR_CHAR; path += key; - return std::make_shared(path, private_init()); + entry = nt::GetEntry(m_inst, path); } + return NetworkTableEntry{entry}; } -NetworkTable::NetworkTable(StringRef path, const private_init&) - : m_path(path) {} +NT_EntryListener NetworkTable::AddEntryListener(TableEntryListener listener, + unsigned int flags) const { + llvm::SmallString<128> path(m_path); + path += PATH_SEPARATOR_CHAR; + std::size_t prefix_len = path.size(); + return nt::AddEntryListener( + m_inst, path, + [=](const EntryNotification& event) { + StringRef relative_key = event.name.substr(prefix_len); + if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) return; + listener(const_cast(this), relative_key, + NetworkTableEntry{event.entry}, event.value, event.flags); + }, + flags); +} -NetworkTable::~NetworkTable() { - for (auto& i : m_listeners) nt::RemoveEntryListener(i.second); +NT_EntryListener NetworkTable::AddEntryListener(StringRef key, + TableEntryListener listener, + unsigned int flags) const { + std::size_t prefix_len = m_path.size() + 1; + auto entry = GetEntry(key); + return nt::AddEntryListener(entry.GetHandle(), + [=](const EntryNotification& event) { + listener(const_cast(this), + event.name.substr(prefix_len), entry, + event.value, event.flags); + }, + flags); +} + +void NetworkTable::RemoveEntryListener(NT_EntryListener listener) const { + nt::RemoveEntryListener(listener); } void NetworkTable::AddTableListener(ITableListener* listener) { @@ -181,13 +198,12 @@ void NetworkTable::AddTableListenerEx(ITableListener* listener, llvm::SmallString<128> path(m_path); path += PATH_SEPARATOR_CHAR; std::size_t prefix_len = path.size(); - unsigned int id = nt::AddEntryListener( - path, - [=](unsigned int /*uid*/, StringRef name, - std::shared_ptr value, unsigned int flags_) { - StringRef relative_key = name.substr(prefix_len); + NT_EntryListener id = nt::AddEntryListener( + m_inst, path, + [=](const EntryNotification& event) { + StringRef relative_key = event.name.substr(prefix_len); if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) return; - listener->ValueChangedEx(this, relative_key, value, flags_); + listener->ValueChangedEx(this, relative_key, event.value, event.flags); }, flags); m_listeners.emplace_back(listener, id); @@ -207,12 +223,12 @@ void NetworkTable::AddTableListenerEx(StringRef key, ITableListener* listener, path += PATH_SEPARATOR_CHAR; std::size_t prefix_len = path.size(); path += key; - unsigned int id = nt::AddEntryListener( - path, - [=](unsigned int /*uid*/, StringRef name, - std::shared_ptr value, unsigned int flags_) { - if (name != path) return; - listener->ValueChangedEx(this, name.substr(prefix_len), value, flags_); + NT_EntryListener id = nt::AddEntryListener( + m_inst, path, + [=](const EntryNotification& event) { + if (event.name != path) return; + listener->ValueChangedEx(this, event.name.substr(prefix_len), + event.value, event.flags); }, flags); m_listeners.emplace_back(listener, id); @@ -235,18 +251,17 @@ void NetworkTable::AddSubTableListener(ITableListener* listener, unsigned int flags = NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE; if (localNotify) flags |= NT_NOTIFY_LOCAL; - unsigned int id = nt::AddEntryListener( - path, - [=](unsigned int /*uid*/, StringRef name, - std::shared_ptr /*value*/, unsigned int flags_) mutable { - StringRef relative_key = name.substr(prefix_len); + NT_EntryListener id = nt::AddEntryListener( + m_inst, path, + [=](const EntryNotification& event) { + StringRef relative_key = event.name.substr(prefix_len); auto end_sub_table = relative_key.find(PATH_SEPARATOR_CHAR); if (end_sub_table == StringRef::npos) return; StringRef sub_table_key = relative_key.substr(0, end_sub_table); if (notified_tables->find(sub_table_key) == notified_tables->end()) return; notified_tables->insert(std::make_pair(sub_table_key, '\0')); - listener->ValueChangedEx(this, sub_table_key, nullptr, flags_); + listener->ValueChangedEx(this, sub_table_key, nullptr, event.flags); }, flags); m_listeners.emplace_back(listener, id); @@ -259,22 +274,19 @@ void NetworkTable::RemoveTableListener(ITableListener* listener) { [=](const Listener& x) { return x.first == listener; }); for (auto i = matches_begin; i != m_listeners.end(); ++i) - nt::RemoveEntryListener(i->second); + RemoveEntryListener(i->second); m_listeners.erase(matches_begin, m_listeners.end()); } -std::shared_ptr NetworkTable::GetSubTable(StringRef key) const { +std::shared_ptr NetworkTable::GetSubTable(StringRef key) const { llvm::SmallString<128> path(m_path); path += PATH_SEPARATOR_CHAR; path += key; - return std::make_shared(path, private_init()); + return std::make_shared(m_inst, path); } bool NetworkTable::ContainsKey(StringRef key) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::GetEntryValue(path) != nullptr; + return GetEntry(key).Exists(); } bool NetworkTable::ContainsSubTable(StringRef key) const { @@ -282,17 +294,20 @@ bool NetworkTable::ContainsSubTable(StringRef key) const { path += PATH_SEPARATOR_CHAR; path += key; path += PATH_SEPARATOR_CHAR; - return !nt::GetEntryInfo(path, 0).empty(); + return !GetEntryInfo(m_inst, path, 0).empty(); } std::vector NetworkTable::GetKeys(int types) const { std::vector keys; llvm::SmallString<128> path(m_path); path += PATH_SEPARATOR_CHAR; - for (auto& entry : nt::GetEntryInfo(path, types)) { - auto relative_key = StringRef(entry.name).substr(path.size()); + auto infos = GetEntryInfo(m_inst, path, types); + std::lock_guard lock(m_mutex); + for (auto& info : infos) { + auto relative_key = StringRef(info.name).substr(path.size()); if (relative_key.find(PATH_SEPARATOR_CHAR) != StringRef::npos) continue; keys.push_back(relative_key); + m_entries[relative_key] = info.entry; } return keys; } @@ -301,7 +316,7 @@ std::vector NetworkTable::GetSubTables() const { std::vector keys; llvm::SmallString<128> path(m_path); path += PATH_SEPARATOR_CHAR; - for (auto& entry : nt::GetEntryInfo(path, 0)) { + for (auto& entry : GetEntryInfo(m_inst, path, 0)) { auto relative_key = StringRef(entry.name).substr(path.size()); std::size_t end_subtable = relative_key.find(PATH_SEPARATOR_CHAR); if (end_subtable == StringRef::npos) continue; @@ -311,242 +326,137 @@ std::vector NetworkTable::GetSubTables() const { } void NetworkTable::SetPersistent(StringRef key) { - SetFlags(key, NT_PERSISTENT); + GetEntry(key).SetPersistent(); } void NetworkTable::ClearPersistent(StringRef key) { - ClearFlags(key, NT_PERSISTENT); + GetEntry(key).ClearPersistent(); } bool NetworkTable::IsPersistent(StringRef key) const { - return (GetFlags(key) & NT_PERSISTENT) != 0; + return GetEntry(key).IsPersistent(); } void NetworkTable::SetFlags(StringRef key, unsigned int flags) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - nt::SetEntryFlags(path, nt::GetEntryFlags(path) | flags); + GetEntry(key).SetFlags(flags); } void NetworkTable::ClearFlags(StringRef key, unsigned int flags) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - nt::SetEntryFlags(path, nt::GetEntryFlags(path) & ~flags); + GetEntry(key).ClearFlags(flags); } unsigned int NetworkTable::GetFlags(StringRef key) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::GetEntryFlags(path); + return GetEntry(key).GetFlags(); } -void NetworkTable::Delete(StringRef key) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - nt::DeleteEntry(path); -} +void NetworkTable::Delete(StringRef key) { GetEntry(key).Delete(); } bool NetworkTable::PutNumber(StringRef key, double value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, nt::Value::MakeDouble(value)); + return GetEntry(key).SetDouble(value); } bool NetworkTable::SetDefaultNumber(StringRef key, double defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, nt::Value::MakeDouble(defaultValue)); + return GetEntry(key).SetDefaultDouble(defaultValue); } double NetworkTable::GetNumber(StringRef key, double defaultValue) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - auto value = nt::GetEntryValue(path); - if (!value || value->type() != NT_DOUBLE) return defaultValue; - return value->GetDouble(); + return GetEntry(key).GetDouble(defaultValue); } bool NetworkTable::PutString(StringRef key, StringRef value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, nt::Value::MakeString(value)); + return GetEntry(key).SetString(value); } bool NetworkTable::SetDefaultString(StringRef key, StringRef defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, nt::Value::MakeString(defaultValue)); + return GetEntry(key).SetDefaultString(defaultValue); } std::string NetworkTable::GetString(StringRef key, StringRef defaultValue) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - auto value = nt::GetEntryValue(path); - if (!value || value->type() != NT_STRING) return defaultValue; - return value->GetString(); + return GetEntry(key).GetString(defaultValue); } bool NetworkTable::PutBoolean(StringRef key, bool value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, nt::Value::MakeBoolean(value)); + return GetEntry(key).SetBoolean(value); } bool NetworkTable::SetDefaultBoolean(StringRef key, bool defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, nt::Value::MakeBoolean(defaultValue)); + return GetEntry(key).SetDefaultBoolean(defaultValue); } bool NetworkTable::GetBoolean(StringRef key, bool defaultValue) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - auto value = nt::GetEntryValue(path); - if (!value || value->type() != NT_BOOLEAN) return defaultValue; - return value->GetBoolean(); + return GetEntry(key).GetBoolean(defaultValue); } bool NetworkTable::PutBooleanArray(llvm::StringRef key, llvm::ArrayRef value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, nt::Value::MakeBooleanArray(value)); + return GetEntry(key).SetBooleanArray(value); } bool NetworkTable::SetDefaultBooleanArray(StringRef key, llvm::ArrayRef defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, - nt::Value::MakeBooleanArray(defaultValue)); + return GetEntry(key).SetDefaultBooleanArray(defaultValue); } std::vector NetworkTable::GetBooleanArray( llvm::StringRef key, llvm::ArrayRef defaultValue) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - auto value = nt::GetEntryValue(path); - if (!value || value->type() != NT_BOOLEAN_ARRAY) return defaultValue; - return value->GetBooleanArray(); + return GetEntry(key).GetBooleanArray(defaultValue); } bool NetworkTable::PutNumberArray(llvm::StringRef key, llvm::ArrayRef value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, nt::Value::MakeDoubleArray(value)); + return GetEntry(key).SetDoubleArray(value); } bool NetworkTable::SetDefaultNumberArray(StringRef key, llvm::ArrayRef defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, - nt::Value::MakeDoubleArray(defaultValue)); + return GetEntry(key).SetDefaultDoubleArray(defaultValue); } std::vector NetworkTable::GetNumberArray( llvm::StringRef key, llvm::ArrayRef defaultValue) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - auto value = nt::GetEntryValue(path); - if (!value || value->type() != NT_DOUBLE_ARRAY) return defaultValue; - return value->GetDoubleArray(); + return GetEntry(key).GetDoubleArray(defaultValue); } bool NetworkTable::PutStringArray(llvm::StringRef key, llvm::ArrayRef value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, nt::Value::MakeStringArray(value)); + return GetEntry(key).SetStringArray(value); } bool NetworkTable::SetDefaultStringArray( StringRef key, llvm::ArrayRef defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, - nt::Value::MakeStringArray(defaultValue)); + return GetEntry(key).SetDefaultStringArray(defaultValue); } std::vector NetworkTable::GetStringArray( llvm::StringRef key, llvm::ArrayRef defaultValue) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - auto value = nt::GetEntryValue(path); - if (!value || value->type() != NT_STRING_ARRAY) return defaultValue; - return value->GetStringArray(); + return GetEntry(key).GetStringArray(defaultValue); } bool NetworkTable::PutRaw(llvm::StringRef key, llvm::StringRef value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, nt::Value::MakeRaw(value)); + return GetEntry(key).SetRaw(value); } bool NetworkTable::SetDefaultRaw(StringRef key, StringRef defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, nt::Value::MakeRaw(defaultValue)); + return GetEntry(key).SetDefaultRaw(defaultValue); } std::string NetworkTable::GetRaw(llvm::StringRef key, llvm::StringRef defaultValue) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - auto value = nt::GetEntryValue(path); - if (!value || value->type() != NT_RAW) return defaultValue; - return value->GetRaw(); + return GetEntry(key).GetRaw(defaultValue); } -bool NetworkTable::PutValue(StringRef key, std::shared_ptr value) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetEntryValue(path, value); +bool NetworkTable::PutValue(StringRef key, std::shared_ptr value) { + return GetEntry(key).SetValue(value); } bool NetworkTable::SetDefaultValue(StringRef key, - std::shared_ptr defaultValue) { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::SetDefaultEntryValue(path, defaultValue); + std::shared_ptr defaultValue) { + return GetEntry(key).SetDefaultValue(defaultValue); } -std::shared_ptr NetworkTable::GetValue(StringRef key) const { - llvm::SmallString<128> path(m_path); - path += PATH_SEPARATOR_CHAR; - path += key; - return nt::GetEntryValue(path); +std::shared_ptr NetworkTable::GetValue(StringRef key) const { + return GetEntry(key).GetValue(); } -StringRef NetworkTable::GetPath() const { - return m_path; -} +StringRef NetworkTable::GetPath() const { return m_path; } diff --git a/src/main/native/cpp/networktables/NetworkTableEntry.cpp b/src/main/native/cpp/networktables/NetworkTableEntry.cpp new file mode 100644 index 0000000..703c844 --- /dev/null +++ b/src/main/native/cpp/networktables/NetworkTableEntry.cpp @@ -0,0 +1,16 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "networktables/NetworkTableEntry.h" + +#include "networktables/NetworkTableInstance.h" + +using namespace nt; + +NetworkTableInstance NetworkTableEntry::GetInstance() const { + return NetworkTableInstance{GetInstanceFromHandle(m_handle)}; +} diff --git a/src/main/native/cpp/networktables/NetworkTableInstance.cpp b/src/main/native/cpp/networktables/NetworkTableInstance.cpp new file mode 100644 index 0000000..1e62f86 --- /dev/null +++ b/src/main/native/cpp/networktables/NetworkTableInstance.cpp @@ -0,0 +1,54 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ +#include "networktables/NetworkTableInstance.h" + +#include "llvm/SmallString.h" + +using namespace nt; + +std::shared_ptr NetworkTableInstance::GetTable( + StringRef key) const { + if (key.empty() || key == "/") { + return std::make_shared(m_handle, ""); + } else if (key[0] == NetworkTable::PATH_SEPARATOR_CHAR) { + return std::make_shared(m_handle, key); + } else { + llvm::SmallString<128> path; + path += NetworkTable::PATH_SEPARATOR_CHAR; + path += key; + return std::make_shared(m_handle, path); + } +} + +void NetworkTableInstance::StartClient(ArrayRef servers, + unsigned int port) { + llvm::SmallVector, 8> server_ports; + for (const auto& server : servers) + server_ports.emplace_back(std::make_pair(server, port)); + StartClient(server_ports); +} + +void NetworkTableInstance::SetServer(ArrayRef servers, + unsigned int port) { + llvm::SmallVector, 8> server_ports; + for (const auto& server : servers) + server_ports.emplace_back(std::make_pair(server, port)); + SetServer(server_ports); +} + +NT_EntryListener NetworkTableInstance::AddEntryListener( + StringRef prefix, + std::function callback, + unsigned int flags) const { + return ::nt::AddEntryListener(m_handle, prefix, callback, flags); +} + +NT_ConnectionListener NetworkTableInstance::AddConnectionListener( + std::function callback, + bool immediate_notify) const { + return ::nt::AddConnectionListener(m_handle, callback, immediate_notify); +} diff --git a/src/main/native/cpp/networktables/RpcCall.cpp b/src/main/native/cpp/networktables/RpcCall.cpp new file mode 100644 index 0000000..bb4c778 --- /dev/null +++ b/src/main/native/cpp/networktables/RpcCall.cpp @@ -0,0 +1,16 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "networktables/RpcCall.h" + +#include "networktables/NetworkTableEntry.h" + +using namespace nt; + +NetworkTableEntry RpcCall::GetEntry() const { + return NetworkTableEntry{m_entry}; +} diff --git a/src/main/native/cpp/ntcore_c.cpp b/src/main/native/cpp/ntcore_c.cpp index 06216b1..533107c 100644 --- a/src/main/native/cpp/ntcore_c.cpp +++ b/src/main/native/cpp/ntcore_c.cpp @@ -11,6 +11,7 @@ #include #include "support/timestamp.h" + #include "Value_internal.h" using namespace nt; @@ -24,6 +25,7 @@ static void ConvertToC(llvm::StringRef in, char** out) { } static void ConvertToC(const EntryInfo& in, NT_EntryInfo* out) { + out->entry = in.entry; ConvertToC(in.name, &out->name); out->type = in.type; out->flags = in.flags; @@ -65,11 +67,50 @@ static void ConvertToC(const RpcDefinition& in, NT_RpcDefinition* out) { ConvertToC(in.results[i], &out->results[i]); } -static void ConvertToC(const RpcCallInfo& in, NT_RpcCallInfo* out) { - out->rpc_id = in.rpc_id; - out->call_uid = in.call_uid; +static void ConvertToC(const RpcAnswer& in, NT_RpcAnswer* out) { + out->entry = in.entry; + out->call = in.call; ConvertToC(in.name, &out->name); ConvertToC(in.params, &out->params); + ConvertToC(in.conn, &out->conn); +} + +static void ConvertToC(const EntryNotification& in, NT_EntryNotification* out) { + out->listener = in.listener; + out->entry = in.entry; + ConvertToC(in.name, &out->name); + ConvertToC(*in.value, &out->value); + out->flags = in.flags; +} + +static void ConvertToC(const ConnectionNotification& in, + NT_ConnectionNotification* out) { + out->listener = in.listener; + out->connected = in.connected; + ConvertToC(in.conn, &out->conn); +} + +static void ConvertToC(const LogMessage& in, NT_LogMessage* out) { + out->logger = in.logger; + out->level = in.level; + out->filename = in.filename; + out->line = in.line; + ConvertToC(in.message, &out->message); +} + +template +static void ConvertToC(const std::vector& in, O** out, size_t* out_len) { + if (!out || !out_len) return; + *out_len = in.size(); + if (in.empty()) { + *out = nullptr; + return; + } + *out = static_cast(std::malloc(sizeof(O*) * in.size())); + for (size_t i = 0; i < in.size(); ++i) { + out[i] = static_cast(std::malloc(sizeof(O))); + ConvertToC(in[i], out[i]); + } } static void DisposeConnectionInfo(NT_ConnectionInfo* info) { @@ -79,6 +120,15 @@ static void DisposeConnectionInfo(NT_ConnectionInfo* info) { static void DisposeEntryInfo(NT_EntryInfo* info) { std::free(info->name.str); } +static void DisposeEntryNotification(NT_EntryNotification* info) { + std::free(info->name.str); + NT_DisposeValue(&info->value); +} + +static void DisposeConnectionNotification(NT_ConnectionNotification* info) { + DisposeConnectionInfo(&info->conn); +} + static RpcParamDef ConvertFromC(const NT_RpcParamDef& in) { RpcParamDef out; out.name = ConvertFromC(in.name); @@ -111,51 +161,90 @@ static RpcDefinition ConvertFromC(const NT_RpcDefinition& in) { extern "C" { +/* + * Instance Functions + */ + +NT_Inst NT_GetDefaultInstance(void) { return nt::GetDefaultInstance(); } + +NT_Inst NT_CreateInstance(void) { return nt::CreateInstance(); } + +void NT_DestroyInstance(NT_Inst inst) { return nt::DestroyInstance(inst); } + +NT_Inst NT_GetInstanceFromHandle(NT_Handle handle) { + return nt::GetInstanceFromHandle(handle); +} + /* * Table Functions */ -void NT_GetEntryValue(const char* name, size_t name_len, - struct NT_Value* value) { +NT_Entry NT_GetEntry(NT_Inst inst, const char* name, size_t name_len) { + return nt::GetEntry(inst, StringRef(name, name_len)); +} + +NT_Entry* NT_GetEntries(NT_Inst inst, const char* prefix, size_t prefix_len, + unsigned int types, size_t* count) { + auto info_v = nt::GetEntries(inst, StringRef(prefix, prefix_len), types); + *count = info_v.size(); + if (info_v.size() == 0) return nullptr; + + // create array and copy into it + NT_Entry* info = + static_cast(std::malloc(info_v.size() * sizeof(NT_Entry))); + std::memcpy(info, info_v.data(), info_v.size() * sizeof(NT_Entry)); + return info; +} + +char* NT_GetEntryName(NT_Entry entry, size_t* name_len) { + struct NT_String v_name; + nt::ConvertToC(nt::GetEntryName(entry), &v_name); + *name_len = v_name.len; + return v_name.str; +} + +enum NT_Type NT_GetEntryType(NT_Entry entry) { return nt::GetEntryType(entry); } + +unsigned long long NT_GetEntryLastChange(NT_Entry entry) { + return nt::GetEntryLastChange(entry); +} + +void NT_GetEntryValue(NT_Entry entry, struct NT_Value* value) { NT_InitValue(value); - auto v = nt::GetEntryValue(StringRef(name, name_len)); + auto v = nt::GetEntryValue(entry); if (!v) return; ConvertToC(*v, value); } -int NT_SetDefaultEntryValue(const char* name, size_t name_len, - const struct NT_Value* set_value) { - return nt::SetDefaultEntryValue(StringRef(name, name_len), - ConvertFromC(*set_value)); +int NT_SetDefaultEntryValue(NT_Entry entry, + const struct NT_Value* default_value) { + return nt::SetDefaultEntryValue(entry, ConvertFromC(*default_value)); } -int NT_SetEntryValue(const char* name, size_t name_len, - const struct NT_Value* value) { - return nt::SetEntryValue(StringRef(name, name_len), ConvertFromC(*value)); +int NT_SetEntryValue(NT_Entry entry, const struct NT_Value* value) { + return nt::SetEntryValue(entry, ConvertFromC(*value)); } -void NT_SetEntryTypeValue(const char* name, size_t name_len, - const struct NT_Value* value) { - nt::SetEntryTypeValue(StringRef(name, name_len), ConvertFromC(*value)); +void NT_SetEntryTypeValue(NT_Entry entry, const struct NT_Value* value) { + nt::SetEntryTypeValue(entry, ConvertFromC(*value)); } -void NT_SetEntryFlags(const char* name, size_t name_len, unsigned int flags) { - nt::SetEntryFlags(StringRef(name, name_len), flags); +void NT_SetEntryFlags(NT_Entry entry, unsigned int flags) { + nt::SetEntryFlags(entry, flags); } -unsigned int NT_GetEntryFlags(const char* name, size_t name_len) { - return nt::GetEntryFlags(StringRef(name, name_len)); +unsigned int NT_GetEntryFlags(NT_Entry entry) { + return nt::GetEntryFlags(entry); } -void NT_DeleteEntry(const char* name, size_t name_len) { - nt::DeleteEntry(StringRef(name, name_len)); -} +void NT_DeleteEntry(NT_Entry entry) { nt::DeleteEntry(entry); } -void NT_DeleteAllEntries(void) { nt::DeleteAllEntries(); } +void NT_DeleteAllEntries(NT_Inst inst) { nt::DeleteAllEntries(inst); } -struct NT_EntryInfo* NT_GetEntryInfo(const char* prefix, size_t prefix_len, - unsigned int types, size_t* count) { - auto info_v = nt::GetEntryInfo(StringRef(prefix, prefix_len), types); +struct NT_EntryInfo* NT_GetEntryInfo(NT_Inst inst, const char* prefix, + size_t prefix_len, unsigned int types, + size_t* count) { + auto info_v = nt::GetEntryInfo(inst, StringRef(prefix, prefix_len), types); *count = info_v.size(); if (info_v.size() == 0) return nullptr; @@ -166,118 +255,216 @@ struct NT_EntryInfo* NT_GetEntryInfo(const char* prefix, size_t prefix_len, return info; } -void NT_Flush(void) { nt::Flush(); } +NT_Bool NT_GetEntryInfoHandle(NT_Entry entry, struct NT_EntryInfo* info) { + auto info_v = nt::GetEntryInfo(entry); + if (info_v.name.empty()) return false; + ConvertToC(info_v, info); + return true; +} /* * Callback Creation Functions */ -void NT_SetListenerOnStart(void (*on_start)(void* data), void* data) { - nt::SetListenerOnStart([=]() { on_start(data); }); +NT_EntryListener NT_AddEntryListener(NT_Inst inst, const char* prefix, + size_t prefix_len, void* data, + NT_EntryListenerCallback callback, + unsigned int flags) { + return nt::AddEntryListener(inst, StringRef(prefix, prefix_len), + [=](const EntryNotification& event) { + NT_EntryNotification c_event; + ConvertToC(event, &c_event); + callback(data, &c_event); + DisposeEntryNotification(&c_event); + }, + flags); +} + +NT_EntryListener NT_AddEntryListenerSingle(NT_Entry entry, void* data, + NT_EntryListenerCallback callback, + unsigned int flags) { + return nt::AddEntryListener(entry, + [=](const EntryNotification& event) { + NT_EntryNotification c_event; + ConvertToC(event, &c_event); + callback(data, &c_event); + DisposeEntryNotification(&c_event); + }, + flags); +} + +NT_EntryListenerPoller NT_CreateEntryListenerPoller(NT_Inst inst) { + return nt::CreateEntryListenerPoller(inst); +} + +void NT_DestroyEntryListenerPoller(NT_EntryListenerPoller poller) { + nt::DestroyEntryListenerPoller(poller); +} + +NT_EntryListener NT_AddPolledEntryListener(NT_EntryListenerPoller poller, + const char* prefix, + size_t prefix_len, + unsigned int flags) { + return nt::AddPolledEntryListener(poller, StringRef(prefix, prefix_len), + flags); +} + +NT_EntryListener NT_AddPolledEntryListenerSingle(NT_EntryListenerPoller poller, + NT_Entry entry, + unsigned int flags) { + return nt::AddPolledEntryListener(poller, entry, flags); +} + +struct NT_EntryNotification* NT_PollEntryListener(NT_EntryListenerPoller poller, + size_t* len) { + auto arr_cpp = nt::PollEntryListener(poller); + NT_EntryNotification* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; +} + +struct NT_EntryNotification* NT_PollEntryListenerTimeout( + NT_EntryListenerPoller poller, size_t* len, double timeout, + NT_Bool* timed_out) { + bool cpp_timed_out = false; + auto arr_cpp = nt::PollEntryListener(poller, timeout, &cpp_timed_out); + *timed_out = cpp_timed_out; + NT_EntryNotification* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; +} + +void NT_CancelPollEntryListener(NT_EntryListenerPoller poller) { + nt::CancelPollEntryListener(poller); +} + +void NT_RemoveEntryListener(NT_EntryListener entry_listener) { + nt::RemoveEntryListener(entry_listener); } -void NT_SetListenerOnExit(void (*on_exit)(void* data), void* data) { - nt::SetListenerOnExit([=]() { on_exit(data); }); +NT_Bool NT_WaitForEntryListenerQueue(NT_Inst inst, double timeout) { + return nt::WaitForEntryListenerQueue(inst, timeout); } -unsigned int NT_AddEntryListener(const char* prefix, size_t prefix_len, - void* data, NT_EntryListenerCallback callback, - unsigned int flags) { - return nt::AddEntryListener( - StringRef(prefix, prefix_len), - [=](unsigned int uid, StringRef name, std::shared_ptr value, - unsigned int flags_) { - callback(uid, data, name.data(), name.size(), &value->value(), flags_); - }, - flags); +NT_ConnectionListener NT_AddConnectionListener( + NT_Inst inst, void* data, NT_ConnectionListenerCallback callback, + NT_Bool immediate_notify) { + return nt::AddConnectionListener(inst, + [=](const ConnectionNotification& event) { + NT_ConnectionNotification event_c; + ConvertToC(event, &event_c); + callback(data, &event_c); + DisposeConnectionNotification(&event_c); + }, + immediate_notify != 0); } -void NT_RemoveEntryListener(unsigned int entry_listener_uid) { - nt::RemoveEntryListener(entry_listener_uid); +NT_ConnectionListenerPoller NT_CreateConnectionListenerPoller(NT_Inst inst) { + return nt::CreateConnectionListenerPoller(inst); } -unsigned int NT_AddConnectionListener(void* data, - NT_ConnectionListenerCallback callback, - int immediate_notify) { - return nt::AddConnectionListener( - [=](unsigned int uid, bool connected, const ConnectionInfo& conn) { - NT_ConnectionInfo conn_c; - ConvertToC(conn, &conn_c); - callback(uid, data, connected ? 1 : 0, &conn_c); - DisposeConnectionInfo(&conn_c); - }, - immediate_notify != 0); +void NT_DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller) { + nt::DestroyConnectionListenerPoller(poller); } -void NT_RemoveConnectionListener(unsigned int conn_listener_uid) { - nt::RemoveConnectionListener(conn_listener_uid); +NT_ConnectionListener NT_AddPolledConnectionListener( + NT_ConnectionListenerPoller poller, NT_Bool immediate_notify) { + return nt::AddPolledConnectionListener(poller, immediate_notify); } -int NT_NotifierDestroyed() { return nt::NotifierDestroyed(); } +struct NT_ConnectionNotification* NT_PollConnectionListener( + NT_ConnectionListenerPoller poller, size_t* len) { + auto arr_cpp = nt::PollConnectionListener(poller); + NT_ConnectionNotification* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; +} + +struct NT_ConnectionNotification* NT_PollConnectionListenerTimeout( + NT_ConnectionListenerPoller poller, size_t* len, double timeout, + NT_Bool* timed_out) { + bool cpp_timed_out = false; + auto arr_cpp = nt::PollConnectionListener(poller, timeout, &cpp_timed_out); + *timed_out = cpp_timed_out; + NT_ConnectionNotification* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; +} + +void NT_CancelPollConnectionListener(NT_ConnectionListenerPoller poller) { + nt::CancelPollConnectionListener(poller); +} + +void NT_RemoveConnectionListener(NT_ConnectionListener conn_listener) { + nt::RemoveConnectionListener(conn_listener); +} + +NT_Bool NT_WaitForConnectionListenerQueue(NT_Inst inst, double timeout) { + return nt::WaitForConnectionListenerQueue(inst, timeout); +} /* * Remote Procedure Call Functions */ -void NT_SetRpcServerOnStart(void (*on_start)(void* data), void* data) { - nt::SetRpcServerOnStart([=]() { on_start(data); }); +void NT_CreateRpc(NT_Entry entry, const char* def, size_t def_len, void* data, + NT_RpcCallback callback) { + nt::CreateRpc(entry, StringRef(def, def_len), [=](const RpcAnswer& answer) { + NT_RpcAnswer answer_c; + ConvertToC(answer, &answer_c); + callback(data, &answer_c); + NT_DisposeRpcAnswer(&answer_c); + }); } -void NT_SetRpcServerOnExit(void (*on_exit)(void* data), void* data) { - nt::SetRpcServerOnExit([=]() { on_exit(data); }); +NT_RpcCallPoller NT_CreateRpcCallPoller(NT_Inst inst) { + return nt::CreateRpcCallPoller(inst); } -void NT_CreateRpc(const char* name, size_t name_len, const char* def, - size_t def_len, void* data, NT_RpcCallback callback) { - nt::CreateRpc(StringRef(name, name_len), StringRef(def, def_len), - [=](StringRef name, StringRef params, - const ConnectionInfo& conn_info) -> std::string { - size_t results_len; - NT_ConnectionInfo conn_c; - ConvertToC(conn_info, &conn_c); - char* results_c = - callback(data, name.data(), name.size(), params.data(), - params.size(), &results_len, &conn_c); - std::string results(results_c, results_len); - std::free(results_c); - DisposeConnectionInfo(&conn_c); - return results; - }); +void NT_DestroyRpcCallPoller(NT_RpcCallPoller poller) { + nt::DestroyRpcCallPoller(poller); } -void NT_CreatePolledRpc(const char* name, size_t name_len, const char* def, - size_t def_len) { - nt::CreatePolledRpc(StringRef(name, name_len), StringRef(def, def_len)); +void NT_CreatePolledRpc(NT_Entry entry, const char* def, size_t def_len, + NT_RpcCallPoller poller) { + nt::CreatePolledRpc(entry, StringRef(def, def_len), poller); } -int NT_PollRpc(int blocking, NT_RpcCallInfo* call_info) { - RpcCallInfo call_info_cpp; - if (!nt::PollRpc(blocking != 0, &call_info_cpp)) return 0; - ConvertToC(call_info_cpp, call_info); - return 1; +NT_RpcAnswer* NT_PollRpc(NT_RpcCallPoller poller, size_t* len) { + auto arr_cpp = nt::PollRpc(poller); + NT_RpcAnswer* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; } -int NT_PollRpcTimeout(int blocking, double time_out, - NT_RpcCallInfo* call_info) { - RpcCallInfo call_info_cpp; - if (!nt::PollRpc(blocking != 0, time_out, &call_info_cpp)) return 0; - ConvertToC(call_info_cpp, call_info); - return 1; +NT_RpcAnswer* NT_PollRpcTimeout(NT_RpcCallPoller poller, size_t* len, + double timeout, NT_Bool* timed_out) { + bool cpp_timed_out = false; + auto arr_cpp = nt::PollRpc(poller, timeout, &cpp_timed_out); + *timed_out = cpp_timed_out; + NT_RpcAnswer* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; } -void NT_PostRpcResponse(unsigned int rpc_id, unsigned int call_uid, - const char* result, size_t result_len) { - nt::PostRpcResponse(rpc_id, call_uid, StringRef(result, result_len)); +void NT_CancelPollRpc(NT_RpcCallPoller poller) { nt::CancelPollRpc(poller); } + +NT_Bool NT_WaitForRpcCallQueue(NT_Inst inst, double timeout) { + return nt::WaitForRpcCallQueue(inst, timeout); +} + +void NT_PostRpcResponse(NT_Entry entry, NT_RpcCall call, const char* result, + size_t result_len) { + nt::PostRpcResponse(entry, call, StringRef(result, result_len)); } -unsigned int NT_CallRpc(const char* name, size_t name_len, const char* params, - size_t params_len) { - return nt::CallRpc(StringRef(name, name_len), StringRef(params, params_len)); +NT_RpcCall NT_CallRpc(NT_Entry entry, const char* params, size_t params_len) { + return nt::CallRpc(entry, StringRef(params, params_len)); } -char* NT_GetRpcResult(int blocking, unsigned int call_uid, size_t* result_len) { +char* NT_GetRpcResult(NT_Entry entry, NT_RpcCall call, size_t* result_len) { std::string result; - if (!nt::GetRpcResult(blocking != 0, call_uid, &result)) return nullptr; + if (!nt::GetRpcResult(entry, call, &result)) return nullptr; // convert result *result_len = result.size(); @@ -286,12 +473,17 @@ char* NT_GetRpcResult(int blocking, unsigned int call_uid, size_t* result_len) { return result_cstr; } -char* NT_GetRpcResultTimeout(int blocking, unsigned int call_uid, - double time_out, size_t* result_len) { +char* NT_GetRpcResultTimeout(NT_Entry entry, NT_RpcCall call, + size_t* result_len, double timeout, + NT_Bool* timed_out) { std::string result; - if (!nt::GetRpcResult(blocking != 0, call_uid, time_out, &result)) + bool cpp_timed_out = false; + if (!nt::GetRpcResult(entry, call, &result, timeout, &cpp_timed_out)) { + *timed_out = cpp_timed_out; return nullptr; + } + *timed_out = cpp_timed_out; // convert result *result_len = result.size(); char* result_cstr; @@ -299,8 +491,8 @@ char* NT_GetRpcResultTimeout(int blocking, unsigned int call_uid, return result_cstr; } -void NT_CancelBlockingRpcResult(unsigned int call_uid) { - nt::CancelBlockingRpcResult(call_uid); +void NT_CancelRpcResult(NT_Entry entry, NT_RpcCall call) { + nt::CancelRpcResult(entry, call); } char* NT_PackRpcDefinition(const NT_RpcDefinition* def, size_t* packed_len) { @@ -313,8 +505,8 @@ char* NT_PackRpcDefinition(const NT_RpcDefinition* def, size_t* packed_len) { return packed_cstr; } -int NT_UnpackRpcDefinition(const char* packed, size_t packed_len, - NT_RpcDefinition* def) { +NT_Bool NT_UnpackRpcDefinition(const char* packed, size_t packed_len, + NT_RpcDefinition* def) { nt::RpcDefinition def_v; if (!nt::UnpackRpcDefinition(StringRef(packed, packed_len), &def_v)) return 0; @@ -361,63 +553,75 @@ NT_Value** NT_UnpackRpcValues(const char* packed, size_t packed_len, * Client/Server Functions */ -void NT_SetNetworkIdentity(const char* name, size_t name_len) { - nt::SetNetworkIdentity(StringRef(name, name_len)); +void NT_SetNetworkIdentity(NT_Inst inst, const char* name, size_t name_len) { + nt::SetNetworkIdentity(inst, StringRef(name, name_len)); } -unsigned int NT_GetNetworkMode() { - return nt::GetNetworkMode(); +unsigned int NT_GetNetworkMode(NT_Inst inst) { + return nt::GetNetworkMode(inst); } -void NT_StartServer(const char* persist_filename, const char* listen_address, - unsigned int port) { - nt::StartServer(persist_filename, listen_address, port); +void NT_StartServer(NT_Inst inst, const char* persist_filename, + const char* listen_address, unsigned int port) { + nt::StartServer(inst, persist_filename, listen_address, port); } -void NT_StopServer(void) { nt::StopServer(); } +void NT_StopServer(NT_Inst inst) { nt::StopServer(inst); } -void NT_StartClientNone(void) { nt::StartClient(); } +void NT_StartClientNone(NT_Inst inst) { nt::StartClient(inst); } -void NT_StartClient(const char* server_name, unsigned int port) { - nt::StartClient(server_name, port); +void NT_StartClient(NT_Inst inst, const char* server_name, unsigned int port) { + nt::StartClient(inst, server_name, port); } -void NT_StartClientMulti(size_t count, const char** server_names, +void NT_StartClientMulti(NT_Inst inst, size_t count, const char** server_names, const unsigned int* ports) { std::vector> servers; servers.reserve(count); for (size_t i = 0; i < count; ++i) servers.emplace_back(std::make_pair(server_names[i], ports[i])); - nt::StartClient(servers); + nt::StartClient(inst, servers); } -void NT_StopClient(void) { nt::StopClient(); } +void NT_StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port) { + nt::StartClientTeam(inst, team, port); +} + +void NT_StopClient(NT_Inst inst) { nt::StopClient(inst); } -void NT_SetServer(const char* server_name, unsigned int port) { - nt::SetServer(server_name, port); +void NT_SetServer(NT_Inst inst, const char* server_name, unsigned int port) { + nt::SetServer(inst, server_name, port); } -void NT_SetServerMulti(size_t count, const char** server_names, - const unsigned int* ports) { +void NT_SetServerMulti(NT_Inst inst, size_t count, const char** server_names, + const unsigned int* ports) { std::vector> servers; servers.reserve(count); for (size_t i = 0; i < count; ++i) servers.emplace_back(std::make_pair(server_names[i], ports[i])); - nt::SetServer(servers); + nt::SetServer(inst, servers); +} + +void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) { + nt::SetServerTeam(inst, team, port); } -void NT_StartDSClient(unsigned int port) { nt::StartDSClient(port); } +void NT_StartDSClient(NT_Inst inst, unsigned int port) { + nt::StartDSClient(inst, port); +} -void NT_StopDSClient(void) { nt::StopDSClient(); } +void NT_StopDSClient(NT_Inst inst) { nt::StopDSClient(inst); } -void NT_StopRpcServer(void) { nt::StopRpcServer(); } +void NT_SetUpdateRate(NT_Inst inst, double interval) { + nt::SetUpdateRate(inst, interval); +} -void NT_StopNotifier(void) { nt::StopNotifier(); } +void NT_Flush(NT_Inst inst) { nt::Flush(inst); } -void NT_SetUpdateRate(double interval) { nt::SetUpdateRate(interval); } +NT_Bool NT_IsConnected(NT_Inst inst) { return nt::IsConnected(inst); } -struct NT_ConnectionInfo* NT_GetConnections(size_t* count) { - auto conn_v = nt::GetConnections(); +struct NT_ConnectionInfo* NT_GetConnections(NT_Inst inst, size_t* count) { + auto conn_v = nt::GetConnections(inst); *count = conn_v.size(); if (conn_v.size() == 0) return nullptr; @@ -432,13 +636,13 @@ struct NT_ConnectionInfo* NT_GetConnections(size_t* count) { * Persistent Functions */ -const char* NT_SavePersistent(const char* filename) { - return nt::SavePersistent(filename); +const char* NT_SavePersistent(NT_Inst inst, const char* filename) { + return nt::SavePersistent(inst, filename); } -const char* NT_LoadPersistent(const char* filename, +const char* NT_LoadPersistent(NT_Inst inst, const char* filename, void (*warn)(size_t line, const char* msg)) { - return nt::LoadPersistent(filename, warn); + return nt::LoadPersistent(inst, filename, warn); } /* @@ -447,8 +651,56 @@ const char* NT_LoadPersistent(const char* filename, unsigned long long NT_Now() { return wpi::Now(); } -void NT_SetLogger(NT_LogFunc func, unsigned int min_level) { - nt::SetLogger(func, min_level); +NT_Logger NT_AddLogger(NT_Inst inst, void* data, NT_LogFunc func, + unsigned int min_level, unsigned int max_level) { + return nt::AddLogger(inst, + [=](const LogMessage& msg) { + NT_LogMessage msg_c; + ConvertToC(msg, &msg_c); + func(data, &msg_c); + NT_DisposeLogMessage(&msg_c); + }, + min_level, max_level); +} + +NT_LoggerPoller NT_CreateLoggerPoller(NT_Inst inst) { + return nt::CreateLoggerPoller(inst); +} + +void NT_DestroyLoggerPoller(NT_LoggerPoller poller) { + nt::DestroyLoggerPoller(poller); +} + +NT_Logger NT_AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, + unsigned int max_level) { + return nt::AddPolledLogger(poller, min_level, max_level); +} + +struct NT_LogMessage* NT_PollLogger(NT_LoggerPoller poller, size_t* len) { + auto arr_cpp = nt::PollLogger(poller); + NT_LogMessage* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; +} + +struct NT_LogMessage* NT_PollLoggerTimeout(NT_LoggerPoller poller, size_t* len, + double timeout, NT_Bool* timed_out) { + bool cpp_timed_out = false; + auto arr_cpp = nt::PollLogger(poller, timeout, &cpp_timed_out); + *timed_out = cpp_timed_out; + NT_LogMessage* arr; + ConvertToC(arr_cpp, &arr, len); + return arr; +} + +void NT_CancelPollLogger(NT_LoggerPoller poller) { + nt::CancelPollLogger(poller); +} + +void NT_RemoveLogger(NT_Logger logger) { nt::RemoveLogger(logger); } + +NT_Bool NT_WaitForLoggerQueue(NT_Inst inst, double timeout) { + return nt::WaitForLoggerQueue(inst, timeout); } void NT_DisposeValue(NT_Value* value) { @@ -497,11 +749,7 @@ void NT_InitString(NT_String* str) { str->len = 0; } -enum NT_Type NT_GetType(const char* name, size_t name_len) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); - if (!v) return NT_Type::NT_UNASSIGNED; - return v->type(); -} +void NT_DisposeEntryArray(NT_Entry* arr, size_t count) { std::free(arr); } void NT_DisposeConnectionInfoArray(NT_ConnectionInfo* arr, size_t count) { for (size_t i = 0; i < count; i++) DisposeConnectionInfo(&arr[i]); @@ -513,6 +761,34 @@ void NT_DisposeEntryInfoArray(NT_EntryInfo* arr, size_t count) { std::free(arr); } +void NT_DisposeEntryInfo(NT_EntryInfo* info) { DisposeEntryInfo(info); } + +void NT_DisposeEntryNotificationArray(NT_EntryNotification* arr, size_t count) { + for (size_t i = 0; i < count; i++) DisposeEntryNotification(&arr[i]); + std::free(arr); +} + +void NT_DisposeEntryNotification(NT_EntryNotification* info) { + DisposeEntryNotification(info); +} + +void NT_DisposeConnectionNotificationArray(NT_ConnectionNotification* arr, + size_t count) { + for (size_t i = 0; i < count; i++) DisposeConnectionNotification(&arr[i]); + std::free(arr); +} + +void NT_DisposeConnectionNotification(NT_ConnectionNotification* info) { + DisposeConnectionNotification(info); +} + +void NT_DisposeLogMessageArray(NT_LogMessage* arr, size_t count) { + for (size_t i = 0; i < count; i++) NT_DisposeLogMessage(&arr[i]); + std::free(arr); +} + +void NT_DisposeLogMessage(NT_LogMessage* info) { std::free(info->message); } + void NT_DisposeRpcDefinition(NT_RpcDefinition* def) { NT_DisposeString(&def->name); @@ -531,9 +807,15 @@ void NT_DisposeRpcDefinition(NT_RpcDefinition* def) { def->num_results = 0; } -void NT_DisposeRpcCallInfo(NT_RpcCallInfo* call_info) { +void NT_DisposeRpcAnswerArray(NT_RpcAnswer* arr, size_t count) { + for (size_t i = 0; i < count; i++) NT_DisposeRpcAnswer(&arr[i]); + std::free(arr); +} + +void NT_DisposeRpcAnswer(NT_RpcAnswer* call_info) { NT_DisposeString(&call_info->name); NT_DisposeString(&call_info->params); + DisposeConnectionInfo(&call_info->conn); } /* Interop Utility Functions */ @@ -573,96 +855,86 @@ void NT_FreeStringArray(struct NT_String* v_string, size_t arr_size) { std::free(v_string); } -int NT_SetEntryDouble(const char* name, size_t name_len, double v_double, - int force) { +NT_Bool NT_SetEntryDouble(NT_Entry entry, unsigned long long time, + double v_double, NT_Bool force) { if (force != 0) { - nt::SetEntryTypeValue(StringRef(name, name_len), - Value::MakeDouble(v_double)); + nt::SetEntryTypeValue(entry, Value::MakeDouble(v_double, time)); return 1; } else { - return nt::SetEntryValue(StringRef(name, name_len), - Value::MakeDouble(v_double)); + return nt::SetEntryValue(entry, Value::MakeDouble(v_double, time)); } } -int NT_SetEntryBoolean(const char* name, size_t name_len, int v_boolean, - int force) { +NT_Bool NT_SetEntryBoolean(NT_Entry entry, unsigned long long time, + NT_Bool v_boolean, NT_Bool force) { if (force != 0) { - nt::SetEntryTypeValue(StringRef(name, name_len), - Value::MakeBoolean(v_boolean != 0)); + nt::SetEntryTypeValue(entry, Value::MakeBoolean(v_boolean != 0, time)); return 1; } else { - return nt::SetEntryValue(StringRef(name, name_len), - Value::MakeBoolean(v_boolean != 0)); + return nt::SetEntryValue(entry, Value::MakeBoolean(v_boolean != 0, time)); } } -int NT_SetEntryString(const char* name, size_t name_len, const char* str, - size_t str_len, int force) { +NT_Bool NT_SetEntryString(NT_Entry entry, unsigned long long time, + const char* str, size_t str_len, NT_Bool force) { if (force != 0) { - nt::SetEntryTypeValue(StringRef(name, name_len), - Value::MakeString(StringRef(str, str_len))); + nt::SetEntryTypeValue(entry, + Value::MakeString(StringRef(str, str_len), time)); return 1; } else { - return nt::SetEntryValue(StringRef(name, name_len), - Value::MakeString(StringRef(str, str_len))); + return nt::SetEntryValue(entry, + Value::MakeString(StringRef(str, str_len), time)); } } -int NT_SetEntryRaw(const char* name, size_t name_len, const char* raw, - size_t raw_len, int force) { +NT_Bool NT_SetEntryRaw(NT_Entry entry, unsigned long long time, const char* raw, + size_t raw_len, NT_Bool force) { if (force != 0) { - nt::SetEntryTypeValue(StringRef(name, name_len), - Value::MakeRaw(StringRef(raw, raw_len))); + nt::SetEntryTypeValue(entry, Value::MakeRaw(StringRef(raw, raw_len), time)); return 1; } else { - return nt::SetEntryValue(StringRef(name, name_len), - Value::MakeRaw(StringRef(raw, raw_len))); + return nt::SetEntryValue(entry, + Value::MakeRaw(StringRef(raw, raw_len), time)); } } -int NT_SetEntryBooleanArray(const char* name, size_t name_len, const int* arr, - size_t size, int force) { +NT_Bool NT_SetEntryBooleanArray(NT_Entry entry, unsigned long long time, + const NT_Bool* arr, size_t size, + NT_Bool force) { if (force != 0) { nt::SetEntryTypeValue( - StringRef(name, name_len), - Value::MakeBooleanArray(llvm::makeArrayRef(arr, size))); + entry, Value::MakeBooleanArray(llvm::makeArrayRef(arr, size), time)); return 1; } else { return nt::SetEntryValue( - StringRef(name, name_len), - Value::MakeBooleanArray(llvm::makeArrayRef(arr, size))); + entry, Value::MakeBooleanArray(llvm::makeArrayRef(arr, size), time)); } } -int NT_SetEntryDoubleArray(const char* name, size_t name_len, const double* arr, - size_t size, int force) { +NT_Bool NT_SetEntryDoubleArray(NT_Entry entry, unsigned long long time, + const double* arr, size_t size, NT_Bool force) { if (force != 0) { nt::SetEntryTypeValue( - StringRef(name, name_len), - Value::MakeDoubleArray(llvm::makeArrayRef(arr, size))); + entry, Value::MakeDoubleArray(llvm::makeArrayRef(arr, size), time)); return 1; } else { return nt::SetEntryValue( - StringRef(name, name_len), - Value::MakeDoubleArray(llvm::makeArrayRef(arr, size))); + entry, Value::MakeDoubleArray(llvm::makeArrayRef(arr, size), time)); } } -int NT_SetEntryStringArray(const char* name, size_t name_len, - const struct NT_String* arr, size_t size, - int force) { +NT_Bool NT_SetEntryStringArray(NT_Entry entry, unsigned long long time, + const struct NT_String* arr, size_t size, + NT_Bool force) { std::vector v; v.reserve(size); for (size_t i = 0; i < size; ++i) v.push_back(ConvertFromC(arr[i])); if (force != 0) { - nt::SetEntryTypeValue(StringRef(name, name_len), - Value::MakeStringArray(std::move(v))); + nt::SetEntryTypeValue(entry, Value::MakeStringArray(std::move(v), time)); return 1; } else { - return nt::SetEntryValue(StringRef(name, name_len), - Value::MakeStringArray(std::move(v))); + return nt::SetEntryValue(entry, Value::MakeStringArray(std::move(v), time)); } } @@ -671,16 +943,17 @@ enum NT_Type NT_GetValueType(const struct NT_Value* value) { return value->type; } -int NT_GetValueBoolean(const struct NT_Value* value, - unsigned long long* last_change, int* v_boolean) { +NT_Bool NT_GetValueBoolean(const struct NT_Value* value, + unsigned long long* last_change, + NT_Bool* v_boolean) { if (!value || value->type != NT_Type::NT_BOOLEAN) return 0; *v_boolean = value->data.v_boolean; *last_change = value->last_change; return 1; } -int NT_GetValueDouble(const struct NT_Value* value, - unsigned long long* last_change, double* v_double) { +NT_Bool NT_GetValueDouble(const struct NT_Value* value, + unsigned long long* last_change, double* v_double) { if (!value || value->type != NT_Type::NT_DOUBLE) return 0; *last_change = value->last_change; *v_double = value->data.v_double; @@ -707,15 +980,16 @@ char* NT_GetValueRaw(const struct NT_Value* value, return raw; } -int* NT_GetValueBooleanArray(const struct NT_Value* value, - unsigned long long* last_change, - size_t* arr_size) { +NT_Bool* NT_GetValueBooleanArray(const struct NT_Value* value, + unsigned long long* last_change, + size_t* arr_size) { if (!value || value->type != NT_Type::NT_BOOLEAN_ARRAY) return nullptr; *last_change = value->last_change; *arr_size = value->data.arr_boolean.size; - int* arr = (int*)std::malloc(value->data.arr_boolean.size * sizeof(int)); + NT_Bool* arr = + (int*)std::malloc(value->data.arr_boolean.size * sizeof(NT_Bool)); std::memcpy(arr, value->data.arr_boolean.arr, - value->data.arr_boolean.size * sizeof(int)); + value->data.arr_boolean.size * sizeof(NT_Bool)); return arr; } @@ -749,81 +1023,80 @@ NT_String* NT_GetValueStringArray(const struct NT_Value* value, return arr; } -int NT_SetDefaultEntryBoolean(const char* name, size_t name_len, - int default_boolean) { - return nt::SetDefaultEntryValue(StringRef(name, name_len), - Value::MakeBoolean(default_boolean != 0)); +NT_Bool NT_SetDefaultEntryBoolean(NT_Entry entry, unsigned long long time, + NT_Bool default_boolean) { + return nt::SetDefaultEntryValue( + entry, Value::MakeBoolean(default_boolean != 0, time)); } -int NT_SetDefaultEntryDouble(const char* name, size_t name_len, - double default_double) { - return nt::SetDefaultEntryValue(StringRef(name, name_len), - Value::MakeDouble(default_double)); +NT_Bool NT_SetDefaultEntryDouble(NT_Entry entry, unsigned long long time, + double default_double) { + return nt::SetDefaultEntryValue(entry, + Value::MakeDouble(default_double, time)); } -int NT_SetDefaultEntryString(const char* name, size_t name_len, - const char* default_value, size_t default_len) { +NT_Bool NT_SetDefaultEntryString(NT_Entry entry, unsigned long long time, + const char* default_value, + size_t default_len) { return nt::SetDefaultEntryValue( - StringRef(name, name_len), - Value::MakeString(StringRef(default_value, default_len))); + entry, Value::MakeString(StringRef(default_value, default_len), time)); } -int NT_SetDefaultEntryRaw(const char* name, size_t name_len, - const char* default_value, size_t default_len) { +NT_Bool NT_SetDefaultEntryRaw(NT_Entry entry, unsigned long long time, + const char* default_value, size_t default_len) { return nt::SetDefaultEntryValue( - StringRef(name, name_len), - Value::MakeString(StringRef(default_value, default_len))); + entry, Value::MakeRaw(StringRef(default_value, default_len), time)); } -int NT_SetDefaultEntryBooleanArray(const char* name, size_t name_len, - const int* default_value, - size_t default_size) { +NT_Bool NT_SetDefaultEntryBooleanArray(NT_Entry entry, unsigned long long time, + const NT_Bool* default_value, + size_t default_size) { return nt::SetDefaultEntryValue( - StringRef(name, name_len), - Value::MakeBooleanArray(llvm::makeArrayRef(default_value, default_size))); + entry, Value::MakeBooleanArray( + llvm::makeArrayRef(default_value, default_size), time)); } -int NT_SetDefaultEntryDoubleArray(const char* name, size_t name_len, - const double* default_value, - size_t default_size) { +NT_Bool NT_SetDefaultEntryDoubleArray(NT_Entry entry, unsigned long long time, + const double* default_value, + size_t default_size) { return nt::SetDefaultEntryValue( - StringRef(name, name_len), - Value::MakeDoubleArray(llvm::makeArrayRef(default_value, default_size))); + entry, Value::MakeDoubleArray( + llvm::makeArrayRef(default_value, default_size), time)); } -int NT_SetDefaultEntryStringArray(const char* name, size_t name_len, - const struct NT_String* default_value, - size_t default_size) { +NT_Bool NT_SetDefaultEntryStringArray(NT_Entry entry, unsigned long long time, + const struct NT_String* default_value, + size_t default_size) { std::vector vec; vec.reserve(default_size); for (size_t i = 0; i < default_size; ++i) vec.push_back(ConvertFromC(default_value[i])); - return nt::SetDefaultEntryValue(StringRef(name, name_len), - Value::MakeStringArray(std::move(vec))); + return nt::SetDefaultEntryValue(entry, + Value::MakeStringArray(std::move(vec), time)); } -int NT_GetEntryBoolean(const char* name, size_t name_len, - unsigned long long* last_change, int* v_boolean) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); +NT_Bool NT_GetEntryBoolean(NT_Entry entry, unsigned long long* last_change, + NT_Bool* v_boolean) { + auto v = nt::GetEntryValue(entry); if (!v || !v->IsBoolean()) return 0; *v_boolean = v->GetBoolean(); *last_change = v->last_change(); return 1; } -int NT_GetEntryDouble(const char* name, size_t name_len, - unsigned long long* last_change, double* v_double) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); +NT_Bool NT_GetEntryDouble(NT_Entry entry, unsigned long long* last_change, + double* v_double) { + auto v = nt::GetEntryValue(entry); if (!v || !v->IsDouble()) return 0; *last_change = v->last_change(); *v_double = v->GetDouble(); return 1; } -char* NT_GetEntryString(const char* name, size_t name_len, - unsigned long long* last_change, size_t* str_len) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); +char* NT_GetEntryString(NT_Entry entry, unsigned long long* last_change, + size_t* str_len) { + auto v = nt::GetEntryValue(entry); if (!v || !v->IsString()) return nullptr; *last_change = v->last_change(); struct NT_String v_string; @@ -832,9 +1105,9 @@ char* NT_GetEntryString(const char* name, size_t name_len, return v_string.str; } -char* NT_GetEntryRaw(const char* name, size_t name_len, - unsigned long long* last_change, size_t* raw_len) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); +char* NT_GetEntryRaw(NT_Entry entry, unsigned long long* last_change, + size_t* raw_len) { + auto v = nt::GetEntryValue(entry); if (!v || !v->IsRaw()) return nullptr; *last_change = v->last_change(); struct NT_String v_raw; @@ -843,23 +1116,22 @@ char* NT_GetEntryRaw(const char* name, size_t name_len, return v_raw.str; } -int* NT_GetEntryBooleanArray(const char* name, size_t name_len, - unsigned long long* last_change, - size_t* arr_size) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); +NT_Bool* NT_GetEntryBooleanArray(NT_Entry entry, + unsigned long long* last_change, + size_t* arr_size) { + auto v = nt::GetEntryValue(entry); if (!v || !v->IsBooleanArray()) return nullptr; *last_change = v->last_change(); auto vArr = v->GetBooleanArray(); - int* arr = static_cast(std::malloc(vArr.size() * sizeof(int))); + NT_Bool* arr = static_cast(std::malloc(vArr.size() * sizeof(NT_Bool))); *arr_size = vArr.size(); std::copy(vArr.begin(), vArr.end(), arr); return arr; } -double* NT_GetEntryDoubleArray(const char* name, size_t name_len, - unsigned long long* last_change, +double* NT_GetEntryDoubleArray(NT_Entry entry, unsigned long long* last_change, size_t* arr_size) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); + auto v = nt::GetEntryValue(entry); if (!v || !v->IsDoubleArray()) return nullptr; *last_change = v->last_change(); auto vArr = v->GetDoubleArray(); @@ -869,10 +1141,10 @@ double* NT_GetEntryDoubleArray(const char* name, size_t name_len, return arr; } -NT_String* NT_GetEntryStringArray(const char* name, size_t name_len, +NT_String* NT_GetEntryStringArray(NT_Entry entry, unsigned long long* last_change, size_t* arr_size) { - auto v = nt::GetEntryValue(StringRef(name, name_len)); + auto v = nt::GetEntryValue(entry); if (!v || !v->IsStringArray()) return nullptr; *last_change = v->last_change(); auto vArr = v->GetStringArray(); diff --git a/src/main/native/cpp/ntcore_cpp.cpp b/src/main/native/cpp/ntcore_cpp.cpp index 41dfbc5..7f66d98 100644 --- a/src/main/native/cpp/ntcore_cpp.cpp +++ b/src/main/native/cpp/ntcore_cpp.cpp @@ -12,142 +12,665 @@ #include #include "support/timestamp.h" + +#include "Handle.h" +#include "InstanceImpl.h" #include "Log.h" -#include "Dispatcher.h" -#include "DsClient.h" -#include "Notifier.h" -#include "RpcServer.h" -#include "Storage.h" #include "WireDecoder.h" #include "WireEncoder.h" namespace nt { +/* + * Instance Functions + */ + +NT_Inst GetDefaultInstance() { + return Handle{InstanceImpl::GetDefaultIndex(), 0, Handle::kInstance}; +} + +NT_Inst CreateInstance() { + return Handle{InstanceImpl::Alloc(), 0, Handle::kInstance}; +} + +void DestroyInstance(NT_Inst inst) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + if (i < 0) return; + InstanceImpl::Destroy(i); +} + +NT_Inst GetInstanceFromHandle(NT_Handle handle) { + Handle h{handle}; + auto type = h.GetType(); + if (type >= Handle::kConnectionListener && type <= Handle::kRpcCallPoller) + return Handle(h.GetInst(), 0, Handle::kInstance); + + return 0; +} + /* * Table Functions */ +NT_Entry GetEntry(NT_Inst inst, StringRef name) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return 0; + + unsigned int id = ii->storage.GetEntry(name); + if (id == UINT_MAX) return 0; + return Handle(i, id, Handle::kEntry); +} + +std::vector GetEntries(NT_Inst inst, StringRef prefix, + unsigned int types) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return std::vector{}; + + auto arr = ii->storage.GetEntries(prefix, types); + // convert indices to handles + for (auto& val : arr) val = Handle(i, val, Handle::kEntry); + return arr; +} + +std::string GetEntryName(NT_Entry entry) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::string{}; + + return ii->storage.GetEntryName(id); +} + +NT_Type GetEntryType(NT_Entry entry) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return NT_UNASSIGNED; + + return ii->storage.GetEntryType(id); +} + +unsigned long long GetEntryLastChange(NT_Entry entry) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return 0; + + return ii->storage.GetEntryLastChange(id); +} + std::shared_ptr GetEntryValue(StringRef name) { - return Storage::GetInstance().GetEntryValue(name); + return InstanceImpl::GetDefault()->storage.GetEntryValue(name); +} + +std::shared_ptr GetEntryValue(NT_Entry entry) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return nullptr; + + return ii->storage.GetEntryValue(id); } bool SetDefaultEntryValue(StringRef name, std::shared_ptr value) { - return Storage::GetInstance().SetDefaultEntryValue(name, value); + return InstanceImpl::GetDefault()->storage.SetDefaultEntryValue(name, value); +} + +bool SetDefaultEntryValue(NT_Entry entry, std::shared_ptr value) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return false; + + return ii->storage.SetDefaultEntryValue(id, value); } bool SetEntryValue(StringRef name, std::shared_ptr value) { - return Storage::GetInstance().SetEntryValue(name, value); + return InstanceImpl::GetDefault()->storage.SetEntryValue(name, value); +} + +bool SetEntryValue(NT_Entry entry, std::shared_ptr value) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return false; + + return ii->storage.SetEntryValue(id, value); } void SetEntryTypeValue(StringRef name, std::shared_ptr value) { - Storage::GetInstance().SetEntryTypeValue(name, value); + InstanceImpl::GetDefault()->storage.SetEntryTypeValue(name, value); +} + +void SetEntryTypeValue(NT_Entry entry, std::shared_ptr value) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->storage.SetEntryTypeValue(id, value); } void SetEntryFlags(StringRef name, unsigned int flags) { - Storage::GetInstance().SetEntryFlags(name, flags); + InstanceImpl::GetDefault()->storage.SetEntryFlags(name, flags); +} + +void SetEntryFlags(NT_Entry entry, unsigned int flags) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->storage.SetEntryFlags(id, flags); } unsigned int GetEntryFlags(StringRef name) { - return Storage::GetInstance().GetEntryFlags(name); + return InstanceImpl::GetDefault()->storage.GetEntryFlags(name); +} + +unsigned int GetEntryFlags(NT_Entry entry) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return 0; + + return ii->storage.GetEntryFlags(id); +} + +void DeleteEntry(StringRef name) { + InstanceImpl::GetDefault()->storage.DeleteEntry(name); +} + +void DeleteEntry(NT_Entry entry) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->storage.DeleteEntry(id); } -void DeleteEntry(StringRef name) { Storage::GetInstance().DeleteEntry(name); } +void DeleteAllEntries() { + InstanceImpl::GetDefault()->storage.DeleteAllEntries(); +} -void DeleteAllEntries() { Storage::GetInstance().DeleteAllEntries(); } +void DeleteAllEntries(NT_Inst inst) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (i < 0 || !ii) return; + + ii->storage.DeleteAllEntries(); +} std::vector GetEntryInfo(StringRef prefix, unsigned int types) { - return Storage::GetInstance().GetEntryInfo(prefix, types); + return InstanceImpl::GetDefault()->storage.GetEntryInfo(0, prefix, types); +} + +std::vector GetEntryInfo(NT_Inst inst, StringRef prefix, + unsigned int types) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return std::vector{}; + + return ii->storage.GetEntryInfo(i, prefix, types); } -void Flush() { Dispatcher::GetInstance().Flush(); } +EntryInfo GetEntryInfo(NT_Entry entry) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + int i = handle.GetInst(); + auto ii = InstanceImpl::Get(i); + if (id < 0 || !ii) { + EntryInfo info; + info.entry = 0; + info.type = NT_UNASSIGNED; + info.flags = 0; + info.last_change = 0; + return info; + } + + return ii->storage.GetEntryInfo(i, id); +} /* * Callback Creation Functions */ -void SetListenerOnStart(std::function on_start) { - Notifier::GetInstance().SetOnStart(on_start); +NT_EntryListener AddEntryListener(StringRef prefix, + EntryListenerCallback callback, + unsigned int flags) { + return AddEntryListener( + Handle(InstanceImpl::GetDefaultIndex(), 0, Handle::kInstance), prefix, + [=](const EntryNotification& event) { + callback(event.listener, event.name, event.value, event.flags); + }, + flags); +} + +NT_EntryListener AddEntryListener( + NT_Inst inst, StringRef prefix, + std::function callback, + unsigned int flags) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (i < 0 || !ii) return 0; + + unsigned int uid = ii->entry_notifier.Add(callback, prefix, flags); + // perform immediate notifications + if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0) { + for (auto& i : ii->storage.GetEntries(prefix, 0)) { + ii->entry_notifier.NotifyEntry(i, ii->storage.GetEntryName(i), + ii->storage.GetEntryValue(i), + NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid); + } + } + return Handle(i, uid, Handle::kEntryListener); +} + +NT_EntryListener AddEntryListener( + NT_Entry entry, + std::function callback, + unsigned int flags) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + int i = handle.GetInst(); + auto ii = InstanceImpl::Get(i); + if (id < 0 || !ii) return 0; + + unsigned int uid = ii->entry_notifier.Add(callback, id, flags); + // perform immediate notifications + if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0) { + auto name = ii->storage.GetEntryName(id); + auto value = ii->storage.GetEntryValue(id); + // if no name or value, don't notify + if (!name.empty() && value) { + ii->entry_notifier.NotifyEntry(id, name, value, + NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid); + } + } + return Handle(i, uid, Handle::kEntryListener); +} + +NT_EntryListenerPoller CreateEntryListenerPoller(NT_Inst inst) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return 0; + + return Handle(i, ii->entry_notifier.CreatePoller(), + Handle::kEntryListenerPoller); +} + +void DestroyEntryListenerPoller(NT_EntryListenerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kEntryListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->entry_notifier.RemovePoller(id); +} + +NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller, + StringRef prefix, unsigned int flags) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kEntryListenerPoller); + int i = handle.GetInst(); + auto ii = InstanceImpl::Get(i); + if (id < 0 || !ii) return 0; + + unsigned int uid = ii->entry_notifier.AddPolled(id, prefix, flags); + // perform immediate notifications + if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0) { + for (auto& i : ii->storage.GetEntries(prefix, 0)) { + ii->entry_notifier.NotifyEntry(i, ii->storage.GetEntryName(i), + ii->storage.GetEntryValue(i), + NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid); + } + } + return Handle(i, uid, Handle::kEntryListener); +} + +NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller, + NT_Entry entry, unsigned int flags) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + int i = handle.GetInst(); + auto ii = InstanceImpl::Get(i); + if (id < 0 || !ii) return 0; + + Handle phandle{poller}; + int p_id = phandle.GetTypedIndex(Handle::kEntryListenerPoller); + if (p_id < 0) return 0; + if (handle.GetInst() != phandle.GetInst()) return 0; + + unsigned int uid = ii->entry_notifier.AddPolled(p_id, entry, flags); + // perform immediate notifications + if ((flags & NT_NOTIFY_IMMEDIATE) != 0 && (flags & NT_NOTIFY_NEW) != 0) { + auto name = ii->storage.GetEntryName(id); + auto value = ii->storage.GetEntryValue(id); + // if no name or value, don't notify + if (!name.empty() && value) { + ii->entry_notifier.NotifyEntry(id, name, value, + NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, uid); + } + } + return Handle(i, uid, Handle::kEntryListener); +} + +std::vector PollEntryListener( + NT_EntryListenerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kEntryListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->entry_notifier.Poll(static_cast(id)); +} + +std::vector PollEntryListener(NT_EntryListenerPoller poller, + double timeout, + bool* timed_out) { + *timed_out = false; + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kEntryListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->entry_notifier.Poll(static_cast(id), timeout, + timed_out); +} + +void CancelPollEntryListener(NT_EntryListenerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kEntryListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->entry_notifier.CancelPoll(id); +} + +void RemoveEntryListener(NT_EntryListener entry_listener) { + Handle handle{entry_listener}; + int uid = handle.GetTypedIndex(Handle::kEntryListener); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (uid < 0 || !ii) return; + + ii->entry_notifier.Remove(uid); +} + +bool WaitForEntryListenerQueue(NT_Inst inst, double timeout) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return true; + return ii->entry_notifier.WaitForQueue(timeout); +} + +NT_ConnectionListener AddConnectionListener(ConnectionListenerCallback callback, + bool immediate_notify) { + return AddConnectionListener( + Handle(InstanceImpl::GetDefaultIndex(), 0, Handle::kInstance), + [=](const ConnectionNotification& event) { + callback(event.listener, event.connected, event.conn); + }, + immediate_notify); +} + +NT_ConnectionListener AddConnectionListener( + NT_Inst inst, + std::function callback, + bool immediate_notify) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return 0; + + unsigned int uid = ii->connection_notifier.Add(callback); + if (immediate_notify) { + for (auto& conn : ii->dispatcher.GetConnections()) + ii->connection_notifier.NotifyConnection(true, conn, uid); + } + return Handle(i, uid, Handle::kConnectionListener); +} + +NT_ConnectionListenerPoller CreateConnectionListenerPoller(NT_Inst inst) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return 0; + + return Handle(i, ii->connection_notifier.CreatePoller(), + Handle::kConnectionListenerPoller); } -void SetListenerOnExit(std::function on_exit) { - Notifier::GetInstance().SetOnExit(on_exit); +void DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->connection_notifier.RemovePoller(id); } -unsigned int AddEntryListener(StringRef prefix, EntryListenerCallback callback, - unsigned int flags) { - unsigned int uid = - Notifier::GetInstance().AddEntryListener(prefix, callback, flags); - if ((flags & NT_NOTIFY_IMMEDIATE) != 0) - Storage::GetInstance().NotifyEntries(prefix, callback); - return uid; +NT_ConnectionListener AddPolledConnectionListener( + NT_ConnectionListenerPoller poller, bool immediate_notify) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller); + int i = handle.GetInst(); + auto ii = InstanceImpl::Get(i); + if (id < 0 || !ii) return 0; + + unsigned int uid = ii->connection_notifier.AddPolled(id); + // perform immediate notifications + if (immediate_notify) { + for (auto& conn : ii->dispatcher.GetConnections()) + ii->connection_notifier.NotifyConnection(true, conn, uid); + } + return Handle(i, uid, Handle::kConnectionListener); } -void RemoveEntryListener(unsigned int entry_listener_uid) { - Notifier::GetInstance().RemoveEntryListener(entry_listener_uid); +std::vector PollConnectionListener( + NT_ConnectionListenerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->connection_notifier.Poll(static_cast(id)); +} + +std::vector PollConnectionListener( + NT_ConnectionListenerPoller poller, double timeout, bool* timed_out) { + *timed_out = false; + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->connection_notifier.Poll(static_cast(id), timeout, + timed_out); } -unsigned int AddConnectionListener(ConnectionListenerCallback callback, - bool immediate_notify) { - unsigned int uid = Notifier::GetInstance().AddConnectionListener(callback); - if (immediate_notify) Dispatcher::GetInstance().NotifyConnections(callback); - return uid; +void CancelPollConnectionListener(NT_ConnectionListenerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kConnectionListenerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->connection_notifier.CancelPoll(id); } -void RemoveConnectionListener(unsigned int conn_listener_uid) { - Notifier::GetInstance().RemoveConnectionListener(conn_listener_uid); +void RemoveConnectionListener(NT_ConnectionListener conn_listener) { + Handle handle{conn_listener}; + int uid = handle.GetTypedIndex(Handle::kConnectionListener); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (uid < 0 || !ii) return; + + ii->connection_notifier.Remove(uid); } -bool NotifierDestroyed() { return Notifier::destroyed(); } +bool WaitForConnectionListenerQueue(NT_Inst inst, double timeout) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return true; + return ii->connection_notifier.WaitForQueue(timeout); +} /* * Remote Procedure Call Functions */ -void SetRpcServerOnStart(std::function on_start) { - RpcServer::GetInstance().SetOnStart(on_start); +void CreateRpc(NT_Entry entry, StringRef def, + std::function callback) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + // only server can create RPCs + if ((ii->dispatcher.GetNetworkMode() & NT_NET_MODE_SERVER) == 0) return; + if (def.empty() || !callback) return; + + ii->storage.CreateRpc(id, def, ii->rpc_server.Add(callback)); } -void SetRpcServerOnExit(std::function on_exit) { - RpcServer::GetInstance().SetOnExit(on_exit); +NT_RpcCallPoller CreateRpcCallPoller(NT_Inst inst) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return 0; + + return Handle(i, ii->rpc_server.CreatePoller(), Handle::kRpcCallPoller); } -void CreateRpc(StringRef name, StringRef def, RpcCallback callback) { - Storage::GetInstance().CreateRpc(name, def, callback); +void DestroyRpcCallPoller(NT_RpcCallPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kRpcCallPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->rpc_server.RemovePoller(id); } -void CreatePolledRpc(StringRef name, StringRef def) { - Storage::GetInstance().CreatePolledRpc(name, def); +void CreatePolledRpc(NT_Entry entry, StringRef def, NT_RpcCallPoller poller) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + Handle phandle{poller}; + int p_id = phandle.GetTypedIndex(Handle::kRpcCallPoller); + if (p_id < 0) return; + if (handle.GetInst() != phandle.GetInst()) return; + + // only server can create RPCs + if ((ii->dispatcher.GetNetworkMode() & NT_NET_MODE_SERVER) == 0) return; + if (def.empty()) return; + + ii->storage.CreateRpc(id, def, ii->rpc_server.AddPolled(p_id)); +} + +std::vector PollRpc(NT_RpcCallPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kRpcCallPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->rpc_server.Poll(static_cast(id)); +} + +std::vector PollRpc(NT_RpcCallPoller poller, double timeout, + bool* timed_out) { + *timed_out = false; + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kRpcCallPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->rpc_server.Poll(static_cast(id), timeout, timed_out); } -bool PollRpc(bool blocking, RpcCallInfo* call_info) { - return RpcServer::GetInstance().PollRpc(blocking, call_info); +void CancelPollRpc(NT_RpcCallPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kRpcCallPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->rpc_server.CancelPoll(id); } -bool PollRpc(bool blocking, double time_out, RpcCallInfo* call_info) { - return RpcServer::GetInstance().PollRpc(blocking, time_out, call_info); +bool WaitForRpcCallQueue(NT_Inst inst, double timeout) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return true; + return ii->rpc_server.WaitForQueue(timeout); } -void PostRpcResponse(unsigned int rpc_id, unsigned int call_uid, - StringRef result) { - RpcServer::GetInstance().PostRpcResponse(rpc_id, call_uid, result); +void PostRpcResponse(NT_Entry entry, NT_RpcCall call, StringRef result) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + Handle chandle{call}; + int call_uid = chandle.GetTypedIndex(Handle::kRpcCall); + if (call_uid < 0) return; + if (handle.GetInst() != chandle.GetInst()) return; + + ii->rpc_server.PostRpcResponse(id, call_uid, result); } -unsigned int CallRpc(StringRef name, StringRef params) { - return Storage::GetInstance().CallRpc(name, params); +NT_RpcCall CallRpc(NT_Entry entry, StringRef params) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + int i = handle.GetInst(); + auto ii = InstanceImpl::Get(i); + if (id < 0 || !ii) return 0; + + unsigned int call_uid = ii->storage.CallRpc(id, params); + if (call_uid == 0) return 0; + return Handle(i, call_uid, Handle::kRpcCall); } -bool GetRpcResult(bool blocking, unsigned int call_uid, std::string* result) { - return Storage::GetInstance().GetRpcResult(blocking, call_uid, result); +bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return false; + + Handle chandle{call}; + int call_uid = chandle.GetTypedIndex(Handle::kRpcCall); + if (call_uid < 0) return false; + if (handle.GetInst() != chandle.GetInst()) return false; + + return ii->storage.GetRpcResult(id, call_uid, result); } -bool GetRpcResult(bool blocking, unsigned int call_uid, double time_out, - std::string* result) { - return Storage::GetInstance().GetRpcResult(blocking, call_uid, time_out, - result); +bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result, + double timeout, bool* timed_out) { + *timed_out = false; + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return false; + + Handle chandle{call}; + int call_uid = chandle.GetTypedIndex(Handle::kRpcCall); + if (call_uid < 0) return false; + if (handle.GetInst() != chandle.GetInst()) return false; + + return ii->storage.GetRpcResult(id, call_uid, result, timeout, timed_out); } -void CancelBlockingRpcResult(unsigned int call_uid) { - Storage::GetInstance().CancelBlockingRpcResult(call_uid); +void CancelRpcResult(NT_Entry entry, NT_RpcCall call) { + Handle handle{entry}; + int id = handle.GetTypedIndex(Handle::kEntry); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + Handle chandle{call}; + int call_uid = chandle.GetTypedIndex(Handle::kRpcCall); + if (call_uid < 0) return; + if (handle.GetInst() != chandle.GetInst()) return; + + ii->storage.CancelRpcResult(id, call_uid); } std::string PackRpcDefinition(const RpcDefinition& def) { @@ -179,7 +702,8 @@ std::string PackRpcDefinition(const RpcDefinition& def) { bool UnpackRpcDefinition(StringRef packed, RpcDefinition* def) { wpi::raw_mem_istream is(packed.data(), packed.size()); - WireDecoder dec(is, 0x0300); + wpi::Logger logger; + WireDecoder dec(is, 0x0300, logger); if (!dec.Read8(&def->version)) return false; if (!dec.ReadString(&def->name)) return false; @@ -222,7 +746,8 @@ std::string PackRpcValues(ArrayRef> values) { std::vector> UnpackRpcValues(StringRef packed, ArrayRef types) { wpi::raw_mem_istream is(packed.data(), packed.size()); - WireDecoder dec(is, 0x0300); + wpi::Logger logger; + WireDecoder dec(is, 0x0300, logger); std::vector> vec; for (auto type : types) { auto item = dec.ReadValue(type); @@ -232,64 +757,198 @@ std::vector> UnpackRpcValues(StringRef packed, return vec; } +unsigned long long Now() { return wpi::Now(); } + /* * Client/Server Functions */ void SetNetworkIdentity(StringRef name) { - Dispatcher::GetInstance().SetIdentity(name); + InstanceImpl::GetDefault()->dispatcher.SetIdentity(name); +} + +void SetNetworkIdentity(NT_Inst inst, StringRef name) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.SetIdentity(name); } unsigned int GetNetworkMode() { - auto& d = Dispatcher::GetInstance(); - return d.GetNetworkMode(); + return InstanceImpl::GetDefault()->dispatcher.GetNetworkMode(); +} + +unsigned int GetNetworkMode(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return 0; + + return ii->dispatcher.GetNetworkMode(); } void StartServer(StringRef persist_filename, const char* listen_address, unsigned int port) { - Dispatcher::GetInstance().StartServer(persist_filename, listen_address, port); + auto ii = InstanceImpl::GetDefault(); + ii->dispatcher.StartServer(persist_filename, listen_address, port); +} + +void StartServer(NT_Inst inst, StringRef persist_filename, + const char* listen_address, unsigned int port) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.StartServer(persist_filename, listen_address, port); } -void StopServer() { Dispatcher::GetInstance().Stop(); } +void StopServer() { InstanceImpl::GetDefault()->dispatcher.Stop(); } + +void StopServer(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.Stop(); +} + +void StartClient() { InstanceImpl::GetDefault()->dispatcher.StartClient(); } + +void StartClient(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; -void StartClient() { Dispatcher::GetInstance().StartClient(); } + ii->dispatcher.StartClient(); +} void StartClient(const char* server_name, unsigned int port) { - auto& d = Dispatcher::GetInstance(); - d.SetServer(server_name, port); - d.StartClient(); + auto ii = InstanceImpl::GetDefault(); + ii->dispatcher.SetServer(server_name, port); + ii->dispatcher.StartClient(); +} + +void StartClient(NT_Inst inst, const char* server_name, unsigned int port) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.SetServer(server_name, port); + ii->dispatcher.StartClient(); } void StartClient(ArrayRef> servers) { - auto& d = Dispatcher::GetInstance(); - d.SetServer(servers); - d.StartClient(); + auto ii = InstanceImpl::GetDefault(); + ii->dispatcher.SetServer(servers); + ii->dispatcher.StartClient(); +} + +void StartClient(NT_Inst inst, + ArrayRef> servers) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.SetServer(servers); + ii->dispatcher.StartClient(); } -void StopClient() { Dispatcher::GetInstance().Stop(); } +void StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.SetServerTeam(team, port); + ii->dispatcher.StartClient(); +} + +void StopClient() { InstanceImpl::GetDefault()->dispatcher.Stop(); } + +void StopClient(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.Stop(); +} void SetServer(const char* server_name, unsigned int port) { - Dispatcher::GetInstance().SetServer(server_name, port); + InstanceImpl::GetDefault()->dispatcher.SetServer(server_name, port); +} + +void SetServer(NT_Inst inst, const char* server_name, unsigned int port) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.SetServer(server_name, port); } void SetServer(ArrayRef> servers) { - Dispatcher::GetInstance().SetServer(servers); + InstanceImpl::GetDefault()->dispatcher.SetServer(servers); } -void StartDSClient(unsigned int port) { DsClient::GetInstance().Start(port); } +void SetServer(NT_Inst inst, + ArrayRef> servers) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.SetServer(servers); +} -void StopDSClient() { DsClient::GetInstance().Stop(); } +void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; -void StopRpcServer() { RpcServer::GetInstance().Stop(); } + ii->dispatcher.SetServerTeam(team, port); +} + +void StartDSClient(unsigned int port) { + InstanceImpl::GetDefault()->ds_client.Start(port); +} -void StopNotifier() { Notifier::GetInstance().Stop(); } +void StartDSClient(NT_Inst inst, unsigned int port) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->ds_client.Start(port); +} + +void StopDSClient() { InstanceImpl::GetDefault()->ds_client.Stop(); } + +void StopDSClient(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->ds_client.Stop(); +} void SetUpdateRate(double interval) { - Dispatcher::GetInstance().SetUpdateRate(interval); + InstanceImpl::GetDefault()->dispatcher.SetUpdateRate(interval); +} + +void SetUpdateRate(NT_Inst inst, double interval) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.SetUpdateRate(interval); +} + +void Flush() { InstanceImpl::GetDefault()->dispatcher.Flush(); } + +void Flush(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return; + + ii->dispatcher.Flush(); } std::vector GetConnections() { - return Dispatcher::GetInstance().GetConnections(); + return InstanceImpl::GetDefault()->dispatcher.GetConnections(); +} + +std::vector GetConnections(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return std::vector{}; + + return ii->dispatcher.GetConnections(); +} + +bool IsConnected(NT_Inst inst) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return false; + + return ii->dispatcher.IsConnected(); } /* @@ -297,21 +956,133 @@ std::vector GetConnections() { */ const char* SavePersistent(StringRef filename) { - return Storage::GetInstance().SavePersistent(filename, false); + return InstanceImpl::GetDefault()->storage.SavePersistent(filename, false); +} + +const char* SavePersistent(NT_Inst inst, StringRef filename) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return "invalid instance handle"; + + return ii->storage.SavePersistent(filename, false); } const char* LoadPersistent( StringRef filename, std::function warn) { - return Storage::GetInstance().LoadPersistent(filename, warn); + return InstanceImpl::GetDefault()->storage.LoadPersistent(filename, warn); } -unsigned long long Now() { return wpi::Now(); } +const char* LoadPersistent( + NT_Inst inst, StringRef filename, + std::function warn) { + auto ii = InstanceImpl::Get(Handle{inst}.GetTypedInst(Handle::kInstance)); + if (!ii) return "invalid instance handle"; + + return ii->storage.LoadPersistent(filename, warn); +} void SetLogger(LogFunc func, unsigned int min_level) { - Logger& logger = Logger::GetInstance(); - logger.SetLogger(func); - logger.set_min_level(min_level); + auto ii = InstanceImpl::GetDefault(); + static std::mutex mutex; + static unsigned int logger = 0; + std::lock_guard lock(mutex); + if (logger != 0) ii->logger_impl.Remove(logger); + logger = ii->logger_impl.Add( + [=](const LogMessage& msg) { + func(msg.level, msg.filename, msg.line, msg.message.c_str()); + }, + min_level, UINT_MAX); +} + +NT_Logger AddLogger(NT_Inst inst, + std::function func, + unsigned int min_level, unsigned int max_level) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return 0; + + if (min_level < ii->logger.min_level()) ii->logger.set_min_level(min_level); + + return Handle(i, ii->logger_impl.Add(func, min_level, max_level), + Handle::kLogger); +} + +NT_LoggerPoller CreateLoggerPoller(NT_Inst inst) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return 0; + + return Handle(i, ii->logger_impl.CreatePoller(), Handle::kLoggerPoller); +} + +void DestroyLoggerPoller(NT_LoggerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kLoggerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->logger_impl.RemovePoller(id); +} + +NT_Logger AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, + unsigned int max_level) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kLoggerPoller); + int i = handle.GetInst(); + auto ii = InstanceImpl::Get(i); + if (id < 0 || !ii) return 0; + + if (min_level < ii->logger.min_level()) ii->logger.set_min_level(min_level); + + return Handle(i, ii->logger_impl.AddPolled(id, min_level, max_level), + Handle::kLogger); +} + +std::vector PollLogger(NT_LoggerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kLoggerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->logger_impl.Poll(static_cast(id)); +} + +std::vector PollLogger(NT_LoggerPoller poller, double timeout, + bool* timed_out) { + *timed_out = false; + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kLoggerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return std::vector{}; + + return ii->logger_impl.Poll(static_cast(id), timeout, + timed_out); +} + +void CancelPollLogger(NT_LoggerPoller poller) { + Handle handle{poller}; + int id = handle.GetTypedIndex(Handle::kLoggerPoller); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (id < 0 || !ii) return; + + ii->logger_impl.CancelPoll(id); +} + +void RemoveLogger(NT_Logger logger) { + Handle handle{logger}; + int uid = handle.GetTypedIndex(Handle::kLogger); + auto ii = InstanceImpl::Get(handle.GetInst()); + if (uid < 0 || !ii) return; + + ii->logger_impl.Remove(uid); + ii->logger.set_min_level(ii->logger_impl.GetMinLevel()); +} + +bool WaitForLoggerQueue(NT_Inst inst, double timeout) { + int i = Handle{inst}.GetTypedInst(Handle::kInstance); + auto ii = InstanceImpl::Get(i); + if (!ii) return true; + return ii->logger_impl.WaitForQueue(timeout); } } // namespace nt diff --git a/src/main/native/cpp/ntcore_test.cpp b/src/main/native/cpp/ntcore_test.cpp index 9d6cca0..88af6ca 100644 --- a/src/main/native/cpp/ntcore_test.cpp +++ b/src/main/native/cpp/ntcore_test.cpp @@ -226,16 +226,16 @@ struct NT_RpcDefinition* NT_GetRpcDefinitionForTesting( } // No need for free as one already exists in the main library -struct NT_RpcCallInfo* NT_GetRpcCallInfoForTesting( +struct NT_RpcAnswer* NT_GetRpcAnswerForTesting( unsigned int rpc_id, unsigned int call_uid, const char* name, const char* params, size_t params_len, int* struct_size) { - struct NT_RpcCallInfo* info = - static_cast(std::calloc(1, sizeof(NT_RpcCallInfo))); - info->rpc_id = rpc_id; - info->call_uid = call_uid; + struct NT_RpcAnswer* info = + static_cast(std::calloc(1, sizeof(NT_RpcAnswer))); + info->entry = rpc_id; + info->call = call_uid; nt::ConvertToC(llvm::StringRef(name), &info->name); nt::ConvertToC(llvm::StringRef(params, params_len), &info->params); - *struct_size = sizeof(NT_RpcCallInfo); + *struct_size = sizeof(NT_RpcAnswer); return info; } // No need for free as one already exists in the main library diff --git a/src/main/native/include/networktables/EntryListenerFlags.h b/src/main/native/include/networktables/EntryListenerFlags.h new file mode 100644 index 0000000..8e82013 --- /dev/null +++ b/src/main/native/include/networktables/EntryListenerFlags.h @@ -0,0 +1,73 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_ENTRYLISTENERFLAGS_H_ +#define NT_ENTRYLISTENERFLAGS_H_ + +#include "ntcore_c.h" + +namespace nt { + +namespace EntryListenerFlags { + +/** + * Flag values for use with entry listeners. + * + * The flags are a bitmask and must be OR'ed together to indicate the + * combination of events desired to be received. + * + * The constants kNew, kDelete, kUpdate, and kFlags represent different events + * that can occur to entries. + * + * By default, notifications are only generated for remote changes occurring + * after the listener is created. The constants kImmediate and kLocal are + * modifiers that cause notifications to be generated at other times. + */ +enum { + /** Initial listener addition. + * Set this flag to receive immediate notification of entries matching the + * flag criteria (generally only useful when combined with kNew). + */ + kImmediate = NT_NOTIFY_IMMEDIATE, + + /** Changed locally. + * Set this flag to receive notification of both local changes and changes + * coming from remote nodes. By default, notifications are only generated + * for remote changes. Must be combined with some combination of kNew, + * kDelete, kUpdate, and kFlags to receive notifications of those respective + * events. + */ + kLocal = NT_NOTIFY_LOCAL, + + /** Newly created entry. + * Set this flag to receive a notification when an entry is created. + */ + kNew = NT_NOTIFY_NEW, + + /** Entry was deleted. + * Set this flag to receive a notification when an entry is deleted. + */ + kDelete = NT_NOTIFY_DELETE, + + /** Entry's value changed. + * Set this flag to receive a notification when an entry's value (or type) + * changes. + */ + kUpdate = NT_NOTIFY_UPDATE, + + /** Entry's flags changed. + * Set this flag to receive a notification when an entry's flags value + * changes. + */ + kFlags = NT_NOTIFY_FLAGS +}; + +} // namespace EntryListenerFlags + +} // namespace nt + +#endif // NT_ENTRYLISTENERFLAGS_H_ diff --git a/src/main/native/include/networktables/NetworkTable.h b/src/main/native/include/networktables/NetworkTable.h index 0399669..f8d6298 100644 --- a/src/main/native/include/networktables/NetworkTable.h +++ b/src/main/native/include/networktables/NetworkTable.h @@ -12,20 +12,35 @@ #include #include +#include "llvm/StringMap.h" +#include "networktables/NetworkTableEntry.h" +#include "networktables/TableEntryListener.h" +#include "networktables/TableListener.h" +#include "ntcore_c.h" #include "tables/ITable.h" namespace nt { +using llvm::ArrayRef; +using llvm::StringRef; + +class NetworkTableInstance; + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif + /** * A network table that knows its subtable path. */ -class NetworkTable : public ITable { +class NetworkTable final : public ITable { private: - struct private_init {}; - + NT_Inst m_inst; std::string m_path; - std::mutex m_mutex; - typedef std::pair Listener; + mutable std::mutex m_mutex; + mutable llvm::StringMap m_entries; + typedef std::pair Listener; std::vector m_listeners; static std::vector s_ip_addresses; @@ -36,31 +51,48 @@ class NetworkTable : public ITable { static unsigned int s_port; public: - NetworkTable(llvm::StringRef path, const private_init&); + NetworkTable(NT_Inst inst, StringRef path); virtual ~NetworkTable(); + /** + * Gets the instance for the table. + * @return Instance + */ + NetworkTableInstance GetInstance() const; + /** * The path separator for sub-tables and keys - * */ static const char PATH_SEPARATOR_CHAR; /** - * @throws IOException + * Initializes network tables */ + WPI_DEPRECATED( + "use NetworkTableInstance::StartServer() or " + "NetworkTableInstance::StartClient() instead") static void Initialize(); + + /** + * Shuts down network tables + */ + WPI_DEPRECATED( + "use NetworkTableInstance::StopServer() or " + "NetworkTableInstance::StopClient() instead") static void Shutdown(); /** * set that network tables should be a client * This must be called before initialize or GetTable */ + WPI_DEPRECATED("use NetworkTableInstance::StartClient() instead") static void SetClientMode(); /** * set that network tables should be a server * This must be called before initialize or GetTable */ + WPI_DEPRECATED("use NetworkTableInstance::StartServer() instead") static void SetServerMode(); /** @@ -69,30 +101,48 @@ class NetworkTable : public ITable { * This must be called before initialize or GetTable * @param team the team number */ + WPI_DEPRECATED( + "use NetworkTableInstance::SetServerTeam() or " + "NetworkTableInstance::StartClientTeam() instead") static void SetTeam(int team); /** * @param address the adress that network tables will connect to in client * mode */ - static void SetIPAddress(llvm::StringRef address); + WPI_DEPRECATED( + "use NetworkTableInstance::SetServer() or " + "NetworkTableInstance::StartClient() instead") + static void SetIPAddress(StringRef address); /** * @param addresses the addresses that network tables will connect to in * client mode (in round robin order) */ - static void SetIPAddress(llvm::ArrayRef addresses); + WPI_DEPRECATED( + "use NetworkTableInstance::SetServer() or " + "NetworkTableInstance::StartClient() instead") + static void SetIPAddress(ArrayRef addresses); /** - * @param port the port number that network tables will connect to in client - * mode or listen to in server mode + * Set the port number that network tables will connect to in client + * mode or listen to in server mode. + * @param port the port number */ + WPI_DEPRECATED( + "use the appropriate parameters to NetworkTableInstance::SetServer(), " + "NetworkTableInstance::StartClient(), " + "NetworkTableInstance::StartServer(), and " + "NetworkTableInstance::StartDSClient() instead") static void SetPort(unsigned int port); /** - * @param enabled whether to enable the connection to the local DS to get - * the robot IP address (defaults to enabled) + * Enable requesting the server address from the Driver Station. + * @param enabled whether to enable the connection to the local DS */ + WPI_DEPRECATED( + "use NetworkTableInstance::StartDSClient() and " + "NetworkTableInstance::StopDSClient() instead") static void SetDSClientEnabled(bool enabled); /** @@ -100,18 +150,23 @@ class NetworkTable : public ITable { * @param filename the filename that the network tables server uses for * automatic loading and saving of persistent values */ - static void SetPersistentFilename(llvm::StringRef filename); + WPI_DEPRECATED( + "use the appropriate parameter to NetworkTableInstance::StartServer() " + "instead") + static void SetPersistentFilename(StringRef filename); /** * Sets the network identity. * This is provided in the connection info on the remote end. * @param name identity */ - static void SetNetworkIdentity(llvm::StringRef name); + WPI_DEPRECATED("use NetworkTableInstance::SetNetworkIdentity() instead") + static void SetNetworkIdentity(StringRef name); /** * Deletes ALL keys in ALL subtables. Use with caution! */ + WPI_DEPRECATED("use NetworkTableInstance::DeleteAllEntries() instead") static void GlobalDeleteAll(); /** @@ -120,13 +175,16 @@ class NetworkTable : public ITable { * This is primarily useful for synchronizing network updates with * user code. */ + WPI_DEPRECATED("use NetworkTableInstance::Flush() instead") static void Flush(); /** * Set the periodic update rate. + * Sets how frequently updates are sent to other nodes over the network. * * @param interval update interval in seconds (range 0.01 to 1.0) */ + WPI_DEPRECATED("use NetworkTableInstance::SetUpdateRate() instead") static void SetUpdateRate(double interval); /** @@ -135,7 +193,8 @@ class NetworkTable : public ITable { * @param filename file name * @return Error (or nullptr). */ - static const char* SavePersistent(llvm::StringRef filename); + WPI_DEPRECATED("use NetworkTableInstance::SavePersistent() instead") + static const char* SavePersistent(StringRef filename); /** * Loads persistent keys from a file. The server does this automatically. @@ -144,8 +203,9 @@ class NetworkTable : public ITable { * @param warn callback function called for warnings * @return Error (or nullptr). */ + WPI_DEPRECATED("use NetworkTableInstance::LoadPersistent() instead") static const char* LoadPersistent( - llvm::StringRef filename, + StringRef filename, std::function warn); /** @@ -154,34 +214,103 @@ class NetworkTable : public ITable { * This will automatically initialize network tables if it has not been * already. * - * @param key - * the key name + * @param key the key name * @return the network table requested */ - static std::shared_ptr GetTable(llvm::StringRef key); + WPI_DEPRECATED( + "use NetworkTableInstance::GetTable() or " + "NetworkTableInstance::GetEntry() instead") + static std::shared_ptr GetTable(StringRef key); + + /** + * Gets the entry for a subkey. + * @param key the key name + * @return Network table entry. + */ + NetworkTableEntry GetEntry(StringRef key) const; + /** + * Listen to keys only within this table. + * @param listener listener to add + * @param flags EntryListenerFlags bitmask + * @return Listener handle + */ + NT_EntryListener AddEntryListener(TableEntryListener listener, + unsigned int flags) const; + + /** + * Listen to a single key. + * @param key the key name + * @param listener listener to add + * @param flags EntryListenerFlags bitmask + * @return Listener handle + */ + NT_EntryListener AddEntryListener(StringRef key, TableEntryListener listener, + unsigned int flags) const; + + /** + * Remove an entry listener. + * @param listener listener handle + */ + void RemoveEntryListener(NT_EntryListener listener) const; + + /** + * Listen for sub-table creation. + * This calls the listener once for each newly created sub-table. + * It immediately calls the listener for any existing sub-tables. + * @param listener listener to add + * @param localNotify notify local changes as well as remote + * @return Listener handle + */ + NT_EntryListener AddSubTableListener(TableListener listener, + bool localNotify = false) const; + + /** + * Remove a sub-table listener. + * @param listener listener handle + */ + void RemoveTableListener(NT_EntryListener listener) const; + + WPI_DEPRECATED( + "use AddEntryListener() instead with flags value of NT_NOTIFY_NEW | " + "NT_NOTIFY_UPDATE") void AddTableListener(ITableListener* listener) override; + + WPI_DEPRECATED( + "use AddEntryListener() instead with flags value of NT_NOTIFY_NEW | " + "NT_NOTIFY_UPDATE | NT_NOTIFY_IMMEDIATE") void AddTableListener(ITableListener* listener, bool immediateNotify) override; + + WPI_DEPRECATED("use AddEntryListener() instead") void AddTableListenerEx(ITableListener* listener, unsigned int flags) override; - void AddTableListener(llvm::StringRef key, ITableListener* listener, + + WPI_DEPRECATED("use AddEntryListener() instead") + void AddTableListener(StringRef key, ITableListener* listener, bool immediateNotify) override; - void AddTableListenerEx(llvm::StringRef key, ITableListener* listener, + + WPI_DEPRECATED("use AddEntryListener() instead") + void AddTableListenerEx(StringRef key, ITableListener* listener, unsigned int flags) override; + + WPI_DEPRECATED("use AddSubTableListener(TableListener, bool) instead") void AddSubTableListener(ITableListener* listener) override; + + WPI_DEPRECATED("use AddSubTableListener(TableListener, bool) instead") void AddSubTableListener(ITableListener* listener, bool localNotify) override; + + WPI_DEPRECATED("use RemoveTableListener(NT_EntryListener) instead") void RemoveTableListener(ITableListener* listener) override; /** * Returns the table at the specified key. If there is no table at the * specified key, it will create a new table * - * @param key - * the key name + * @param key the key name * @return the networktable to be returned */ - std::shared_ptr GetSubTable(llvm::StringRef key) const override; + std::shared_ptr GetSubTable(StringRef key) const override; /** * Determines whether the given key is in this table. @@ -189,7 +318,7 @@ class NetworkTable : public ITable { * @param key the key to search for * @return true if the table as a value assigned to the given key */ - bool ContainsKey(llvm::StringRef key) const override; + bool ContainsKey(StringRef key) const override; /** * Determines whether there exists a non-empty subtable for this key @@ -199,15 +328,17 @@ class NetworkTable : public ITable { * @return true if there is a subtable with the key which contains at least * one key/subtable of its own */ - bool ContainsSubTable(llvm::StringRef key) const override; + bool ContainsSubTable(StringRef key) const override; /** + * Gets all keys in the table (not including sub-tables). * @param types bitmask of types; 0 is treated as a "don't care". * @return keys currently in the table */ std::vector GetKeys(int types = 0) const override; /** + * Gets the names of all subtables in the table. * @return subtables currently in the table */ std::vector GetSubTables() const override; @@ -217,7 +348,7 @@ class NetworkTable : public ITable { * * @param key the key to make persistent */ - void SetPersistent(llvm::StringRef key) override; + void SetPersistent(StringRef key) override; /** * Stop making a key's value persistent through program restarts. @@ -225,7 +356,7 @@ class NetworkTable : public ITable { * * @param key the key name */ - void ClearPersistent(llvm::StringRef key) override; + void ClearPersistent(StringRef key) override; /** * Returns whether the value is persistent through program restarts. @@ -233,7 +364,7 @@ class NetworkTable : public ITable { * * @param key the key name */ - bool IsPersistent(llvm::StringRef key) const override; + bool IsPersistent(StringRef key) const override; /** * Sets flags on the specified key in this table. The key can @@ -242,7 +373,7 @@ class NetworkTable : public ITable { * @param key the key name * @param flags the flags to set (bitmask) */ - void SetFlags(llvm::StringRef key, unsigned int flags) override; + void SetFlags(StringRef key, unsigned int flags) override; /** * Clears flags on the specified key in this table. The key can @@ -251,7 +382,7 @@ class NetworkTable : public ITable { * @param key the key name * @param flags the flags to clear (bitmask) */ - void ClearFlags(llvm::StringRef key, unsigned int flags) override; + void ClearFlags(StringRef key, unsigned int flags) override; /** * Returns the flags for the specified key. @@ -259,14 +390,14 @@ class NetworkTable : public ITable { * @param key the key name * @return the flags, or 0 if the key is not defined */ - unsigned int GetFlags(llvm::StringRef key) const override; + unsigned int GetFlags(StringRef key) const override; /** * Deletes the specified key in this table. * * @param key the key name */ - void Delete(llvm::StringRef key) override; + void Delete(StringRef key) override; /** * Put a number in the table @@ -275,7 +406,7 @@ class NetworkTable : public ITable { * @param value the value that will be assigned * @return False if the table key already exists with a different type */ - bool PutNumber(llvm::StringRef key, double value) override; + bool PutNumber(StringRef key, double value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -283,8 +414,7 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultNumber(llvm::StringRef key, - double defaultValue) override; + bool SetDefaultNumber(StringRef key, double defaultValue) override; /** * Gets the number associated with the given name. @@ -294,8 +424,7 @@ class NetworkTable : public ITable { * @return the value associated with the given key or the given default value * if there is no value associated with the key */ - virtual double GetNumber(llvm::StringRef key, - double defaultValue) const override; + double GetNumber(StringRef key, double defaultValue) const override; /** * Put a string in the table @@ -304,7 +433,7 @@ class NetworkTable : public ITable { * @param value the value that will be assigned * @return False if the table key already exists with a different type */ - virtual bool PutString(llvm::StringRef key, llvm::StringRef value) override; + bool PutString(StringRef key, StringRef value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -312,8 +441,7 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultString(llvm::StringRef key, - llvm::StringRef defaultValue) override; + bool SetDefaultString(StringRef key, StringRef defaultValue) override; /** * Gets the string associated with the given name. If the key does not @@ -324,8 +452,7 @@ class NetworkTable : public ITable { * @return the value associated with the given key or the given default value * if there is no value associated with the key */ - virtual std::string GetString(llvm::StringRef key, - llvm::StringRef defaultValue) const override; + std::string GetString(StringRef key, StringRef defaultValue) const override; /** * Put a boolean in the table @@ -334,7 +461,7 @@ class NetworkTable : public ITable { * @param value the value that will be assigned * @return False if the table key already exists with a different type */ - virtual bool PutBoolean(llvm::StringRef key, bool value) override; + bool PutBoolean(StringRef key, bool value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -342,8 +469,7 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultBoolean(llvm::StringRef key, - bool defaultValue) override; + bool SetDefaultBoolean(StringRef key, bool defaultValue) override; /** * Gets the boolean associated with the given name. If the key does not @@ -354,8 +480,7 @@ class NetworkTable : public ITable { * @return the value associated with the given key or the given default value * if there is no value associated with the key */ - virtual bool GetBoolean(llvm::StringRef key, - bool defaultValue) const override; + bool GetBoolean(StringRef key, bool defaultValue) const override; /** * Put a boolean array in the table @@ -367,8 +492,7 @@ class NetworkTable : public ITable { * std::vector is special-cased in C++. 0 is false, any * non-zero value is true. */ - virtual bool PutBooleanArray(llvm::StringRef key, - llvm::ArrayRef value) override; + bool PutBooleanArray(StringRef key, ArrayRef value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -376,8 +500,8 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultBooleanArray( - llvm::StringRef key, llvm::ArrayRef defaultValue) override; + bool SetDefaultBooleanArray(StringRef key, + ArrayRef defaultValue) override; /** * Returns the boolean array the key maps to. If the key does not exist or is @@ -394,8 +518,8 @@ class NetworkTable : public ITable { * because std::vector is special-cased in C++. 0 is false, any * non-zero value is true. */ - virtual std::vector GetBooleanArray( - llvm::StringRef key, llvm::ArrayRef defaultValue) const override; + std::vector GetBooleanArray(StringRef key, + ArrayRef defaultValue) const override; /** * Put a number array in the table @@ -403,8 +527,7 @@ class NetworkTable : public ITable { * @param value the value that will be assigned * @return False if the table key already exists with a different type */ - virtual bool PutNumberArray(llvm::StringRef key, - llvm::ArrayRef value) override; + bool PutNumberArray(StringRef key, ArrayRef value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -412,8 +535,8 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultNumberArray( - llvm::StringRef key, llvm::ArrayRef defaultValue) override; + bool SetDefaultNumberArray(StringRef key, + ArrayRef defaultValue) override; /** * Returns the number array the key maps to. If the key does not exist or is @@ -426,8 +549,8 @@ class NetworkTable : public ITable { * @note This makes a copy of the array. If the overhead of this is a * concern, use GetValue() instead. */ - virtual std::vector GetNumberArray( - llvm::StringRef key, llvm::ArrayRef defaultValue) const override; + std::vector GetNumberArray( + StringRef key, ArrayRef defaultValue) const override; /** * Put a string array in the table @@ -435,8 +558,7 @@ class NetworkTable : public ITable { * @param value the value that will be assigned * @return False if the table key already exists with a different type */ - virtual bool PutStringArray(llvm::StringRef key, - llvm::ArrayRef value) override; + bool PutStringArray(StringRef key, ArrayRef value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -444,8 +566,8 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultStringArray( - llvm::StringRef key, llvm::ArrayRef defaultValue) override; + bool SetDefaultStringArray(StringRef key, + ArrayRef defaultValue) override; /** * Returns the string array the key maps to. If the key does not exist or is @@ -458,9 +580,8 @@ class NetworkTable : public ITable { * @note This makes a copy of the array. If the overhead of this is a * concern, use GetValue() instead. */ - virtual std::vector GetStringArray( - llvm::StringRef key, - llvm::ArrayRef defaultValue) const override; + std::vector GetStringArray( + StringRef key, ArrayRef defaultValue) const override; /** * Put a raw value (byte array) in the table @@ -468,7 +589,7 @@ class NetworkTable : public ITable { * @param value the value that will be assigned * @return False if the table key already exists with a different type */ - virtual bool PutRaw(llvm::StringRef key, llvm::StringRef value) override; + bool PutRaw(StringRef key, StringRef value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -476,8 +597,7 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultRaw(llvm::StringRef key, - llvm::StringRef defaultValue) override; + bool SetDefaultRaw(StringRef key, StringRef defaultValue) override; /** * Returns the raw value (byte array) the key maps to. If the key does not @@ -490,8 +610,7 @@ class NetworkTable : public ITable { * @note This makes a copy of the raw contents. If the overhead of this is a * concern, use GetValue() instead. */ - virtual std::string GetRaw(llvm::StringRef key, - llvm::StringRef defaultValue) const override; + std::string GetRaw(StringRef key, StringRef defaultValue) const override; /** * Put a value in the table @@ -500,7 +619,7 @@ class NetworkTable : public ITable { * @param value the value that will be assigned * @return False if the table key already exists with a different type */ - bool PutValue(llvm::StringRef key, std::shared_ptr value) override; + bool PutValue(StringRef key, std::shared_ptr value) override; /** * Gets the current value in the table, setting it if it does not exist. @@ -508,8 +627,8 @@ class NetworkTable : public ITable { * @param defaultValue the default value to set if key doesn't exist. * @returns False if the table key exists with a different type */ - virtual bool SetDefaultValue( - llvm::StringRef key, std::shared_ptr defaultValue) override; + bool SetDefaultValue(StringRef key, + std::shared_ptr defaultValue) override; /** * Gets the value associated with a key as an object @@ -518,14 +637,19 @@ class NetworkTable : public ITable { * @return the value associated with the given key, or nullptr if the key * does not exist */ - std::shared_ptr GetValue(llvm::StringRef key) const override; + std::shared_ptr GetValue(StringRef key) const override; /** - * Gets the full path of this table. + * Gets the full path of this table. Does not include the trailing "/". + * @return The path (e.g "", "/foo"). */ - llvm::StringRef GetPath() const override; + StringRef GetPath() const override; }; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + } // namespace nt // For backwards compatability diff --git a/src/main/native/include/networktables/NetworkTableEntry.h b/src/main/native/include/networktables/NetworkTableEntry.h new file mode 100644 index 0000000..b3b7adb --- /dev/null +++ b/src/main/native/include/networktables/NetworkTableEntry.h @@ -0,0 +1,449 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_ENTRY_H_ +#define NT_ENTRY_H_ + +#include +#include + +#include "llvm/StringRef.h" + +#include "networktables/NetworkTableType.h" +#include "networktables/NetworkTableValue.h" +#include "networktables/RpcCall.h" +#include "ntcore_c.h" +#include "ntcore_cpp.h" + +namespace nt { + +using llvm::ArrayRef; +using llvm::StringRef; + +class NetworkTableInstance; + +/** NetworkTables Entry */ +class NetworkTableEntry final { + public: + /** + * Flag values (as returned by {@link #getFlags()}). + */ + enum Flags { kPersistent = NT_PERSISTENT }; + + /** + * Construct invalid instance. + */ + NetworkTableEntry(); + + /** + * Construct from native handle. + * @param handle Native handle + */ + explicit NetworkTableEntry(NT_Entry handle); + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + explicit operator bool() const { return m_handle != 0; } + + /** + * Gets the native handle for the entry. + * @return Native handle + */ + NT_Entry GetHandle() const; + + /** + * Gets the instance for the entry. + * @return Instance + */ + NetworkTableInstance GetInstance() const; + + /** + * Determines if the entry currently exists. + * @return True if the entry exists, false otherwise. + */ + bool Exists() const; + + /** + * Gets the name of the entry (the key). + * @return the entry's name + */ + std::string GetName() const; + + /** + * Gets the type of the entry. + * @return the entry's type + */ + NetworkTableType GetType() const; + + /** + * Returns the flags. + * @return the flags (bitmask) + */ + unsigned int GetFlags() const; + + /** + * Gets the last time the entry's value was changed. + * @return Entry last change time + */ + unsigned long long GetLastChange() const; + + /** + * Gets combined information about the entry. + * @return Entry information + */ + EntryInfo GetInfo() const; + + /** + * Gets the entry's value. If the entry does not exist, returns nullptr. + * + * @return the entry's value or nullptr if it does not exist. + */ + std::shared_ptr GetValue() const; + + /** + * Gets the entry's value as a boolean. If the entry does not exist or is of + * different type, it will return the default value. + * + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + bool GetBoolean(bool defaultValue) const; + + /** + * Gets the entry's value as a double. If the entry does not exist or is of + * different type, it will return the default value. + * + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + double GetDouble(double defaultValue) const; + + /** + * Gets the entry's value as a string. If the entry does not exist or is of + * different type, it will return the default value. + * + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + std::string GetString(StringRef defaultValue) const; + + /** + * Gets the entry's value as a raw. If the entry does not exist or is of + * different type, it will return the default value. + * + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + */ + std::string GetRaw(StringRef defaultValue) const; + + /** + * Gets the entry's value as a boolean array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + * + * @note This makes a copy of the array. If the overhead of this is a + * concern, use GetValue() instead. + * + * @note The returned array is std::vector instead of std::vector + * because std::vector is special-cased in C++. 0 is false, any + * non-zero value is true. + */ + std::vector GetBooleanArray(ArrayRef defaultValue) const; + + /** + * Gets the entry's value as a double array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + * + * @note This makes a copy of the array. If the overhead of this is a + * concern, use GetValue() instead. + */ + std::vector GetDoubleArray(ArrayRef defaultValue) const; + + /** + * Gets the entry's value as a string array. If the entry does not exist + * or is of different type, it will return the default value. + * @param defaultValue the value to be returned if no value is found + * @return the entry's value or the given default value + * + * @note This makes a copy of the array. If the overhead of this is a + * concern, use GetValue() instead. + */ + std::vector GetStringArray( + ArrayRef defaultValue) const; + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultValue(std::shared_ptr value); + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultBoolean(bool defaultValue); + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultDouble(double defaultValue); + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultString(StringRef defaultValue); + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultRaw(StringRef defaultValue); + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultBooleanArray(ArrayRef defaultValue); + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultDoubleArray(ArrayRef defaultValue); + + /** + * Sets the entry's value if it does not exist. + * @param defaultValue the default value to set + * @return False if the entry exists with a different type + */ + bool SetDefaultStringArray(ArrayRef defaultValue); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetValue(std::shared_ptr value); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetBoolean(bool value); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetDouble(double value); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetString(StringRef value); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetRaw(StringRef value); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetBooleanArray(ArrayRef value); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetDoubleArray(ArrayRef value); + + /** + * Sets the entry's value. + * @param value the value to set + * @return False if the entry exists with a different type + */ + bool SetStringArray(ArrayRef value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetValue(std::shared_ptr value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetBoolean(bool value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetDouble(double value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetString(StringRef value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetRaw(StringRef value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetBooleanArray(ArrayRef value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetDoubleArray(ArrayRef value); + + /** + * Sets the entry's value. If the value is of different type, the type is + * changed to match the new value. + * @param value the value to set + */ + void ForceSetStringArray(ArrayRef value); + + /** + * Sets flags. + * @param flags the flags to set (bitmask) + */ + void SetFlags(unsigned int flags); + + /** + * Clears flags. + * @param flags the flags to clear (bitmask) + */ + void ClearFlags(unsigned int flags); + + /** + * Make value persistent through program restarts. + */ + void SetPersistent(); + + /** + * Stop making value persistent through program restarts. + */ + void ClearPersistent(); + + /** + * Returns whether the value is persistent through program restarts. + * @return True if the value is persistent. + */ + bool IsPersistent() const; + + /** + * Deletes the entry. + */ + void Delete(); + + /** + * Create a callback-based RPC entry point. Only valid to use on the server. + * The callback function will be called when the RPC is called. + * This function creates RPC version 0 definitions (raw data in and out). + * @param callback callback function + */ + void CreateRpc(std::function callback); + + /** + * Create a polled RPC entry point. Only valid to use on the server. + * The caller is responsible for calling NetworkTableInstance::PollRpc() + * to poll for servicing incoming RPC calls. + * This function creates RPC version 0 definitions (raw data in and out). + */ + void CreatePolledRpc(); + + /** + * Call a RPC function. May be used on either the client or server. + * This function is non-blocking. Either RpcCall::GetResult() or + * RpcCall::CancelResult() must be called on the return value to either + * get or ignore the result of the call. + * @param params parameter + * @return RPC call object. + */ + RpcCall CallRpc(StringRef params); + + /** + * Add a listener for changes to this entry. + * + * @param callback listener to add + * @param flags NotifyKind bitmask + * @return Listener handle + */ + NT_EntryListener AddListener( + std::function callback, + unsigned int flags) const; + + /** + * Remove an entry listener. + * @param entry_listener Listener handle to remove + */ + void RemoveListener(NT_EntryListener entry_listener); + + /** + * Equality operator. Returns true if both instances refer to the same + * native handle. + */ + bool operator==(const NetworkTableEntry& oth) const { + return m_handle == oth.m_handle; + } + + /** Inequality operator. */ + bool operator!=(const NetworkTableEntry& oth) const { + return !(*this == oth); + } + + protected: + /* Native handle */ + NT_Entry m_handle; +}; + +} // namespace nt + +#include "networktables/NetworkTableEntry.inl" + +#endif // NT_ENTRY_H_ diff --git a/src/main/native/include/networktables/NetworkTableEntry.inl b/src/main/native/include/networktables/NetworkTableEntry.inl new file mode 100644 index 0000000..bd2ee70 --- /dev/null +++ b/src/main/native/include/networktables/NetworkTableEntry.inl @@ -0,0 +1,232 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_ENTRY_INL_ +#define NT_ENTRY_INL_ + +namespace nt { + +inline NetworkTableEntry::NetworkTableEntry() : m_handle{0} {} + +inline NetworkTableEntry::NetworkTableEntry(NT_Entry handle) + : m_handle{handle} {} + +inline NT_Entry NetworkTableEntry::GetHandle() const { return m_handle; } + +inline bool NetworkTableEntry::Exists() const { + return GetEntryType(m_handle) != NT_UNASSIGNED; +} + +inline std::string NetworkTableEntry::GetName() const { + return GetEntryName(m_handle); +} + +inline NetworkTableType NetworkTableEntry::GetType() const { + return static_cast(GetEntryType(m_handle)); +} + +inline unsigned int NetworkTableEntry::GetFlags() const { + return GetEntryFlags(m_handle); +} + +inline unsigned long long NetworkTableEntry::GetLastChange() const { + return GetEntryLastChange(m_handle); +} + +inline EntryInfo NetworkTableEntry::GetInfo() const { + return GetEntryInfo(m_handle); +} + +inline std::shared_ptr NetworkTableEntry::GetValue() const { + return GetEntryValue(m_handle); +} + +inline bool NetworkTableEntry::GetBoolean(bool defaultValue) const { + auto value = GetEntryValue(m_handle); + if (!value || value->type() != NT_BOOLEAN) return defaultValue; + return value->GetBoolean(); +} + +inline double NetworkTableEntry::GetDouble(double defaultValue) const { + auto value = GetEntryValue(m_handle); + if (!value || value->type() != NT_DOUBLE) return defaultValue; + return value->GetDouble(); +} + +inline std::string NetworkTableEntry::GetString(StringRef defaultValue) const { + auto value = GetEntryValue(m_handle); + if (!value || value->type() != NT_STRING) return defaultValue; + return value->GetString(); +} + +inline std::string NetworkTableEntry::GetRaw(StringRef defaultValue) const { + auto value = GetEntryValue(m_handle); + if (!value || value->type() != NT_RAW) return defaultValue; + return value->GetString(); +} + +inline std::vector NetworkTableEntry::GetBooleanArray( + ArrayRef defaultValue) const { + auto value = GetEntryValue(m_handle); + if (!value || value->type() != NT_BOOLEAN_ARRAY) return defaultValue; + return value->GetBooleanArray(); +} + +inline std::vector NetworkTableEntry::GetDoubleArray( + ArrayRef defaultValue) const { + auto value = GetEntryValue(m_handle); + if (!value || value->type() != NT_DOUBLE_ARRAY) return defaultValue; + return value->GetDoubleArray(); +} + +inline std::vector NetworkTableEntry::GetStringArray( + ArrayRef defaultValue) const { + auto value = GetEntryValue(m_handle); + if (!value || value->type() != NT_STRING_ARRAY) return defaultValue; + return value->GetStringArray(); +} + +inline bool NetworkTableEntry::SetDefaultValue(std::shared_ptr value) { + return SetDefaultEntryValue(m_handle, value); +} + +inline bool NetworkTableEntry::SetDefaultBoolean(bool defaultValue) { + return SetDefaultEntryValue(m_handle, Value::MakeBoolean(defaultValue)); +} + +inline bool NetworkTableEntry::SetDefaultDouble(double defaultValue) { + return SetDefaultEntryValue(m_handle, Value::MakeDouble(defaultValue)); +} + +inline bool NetworkTableEntry::SetDefaultString(StringRef defaultValue) { + return SetDefaultEntryValue(m_handle, Value::MakeString(defaultValue)); +} + +inline bool NetworkTableEntry::SetDefaultRaw(StringRef defaultValue) { + return SetDefaultEntryValue(m_handle, Value::MakeRaw(defaultValue)); +} + +inline bool NetworkTableEntry::SetDefaultBooleanArray( + ArrayRef defaultValue) { + return SetDefaultEntryValue(m_handle, Value::MakeBooleanArray(defaultValue)); +} + +inline bool NetworkTableEntry::SetDefaultDoubleArray( + ArrayRef defaultValue) { + return SetDefaultEntryValue(m_handle, Value::MakeDoubleArray(defaultValue)); +} + +inline bool NetworkTableEntry::SetDefaultStringArray( + ArrayRef defaultValue) { + return SetDefaultEntryValue(m_handle, Value::MakeStringArray(defaultValue)); +} + +inline bool NetworkTableEntry::SetValue(std::shared_ptr value) { + return SetEntryValue(m_handle, value); +} + +inline bool NetworkTableEntry::SetBoolean(bool value) { + return SetEntryValue(m_handle, Value::MakeBoolean(value)); +} + +inline bool NetworkTableEntry::SetDouble(double value) { + return SetEntryValue(m_handle, Value::MakeDouble(value)); +} + +inline bool NetworkTableEntry::SetString(StringRef value) { + return SetEntryValue(m_handle, Value::MakeString(value)); +} + +inline bool NetworkTableEntry::SetRaw(StringRef value) { + return SetEntryValue(m_handle, Value::MakeRaw(value)); +} + +inline bool NetworkTableEntry::SetBooleanArray(ArrayRef value) { + return SetEntryValue(m_handle, Value::MakeBooleanArray(value)); +} + +inline bool NetworkTableEntry::SetDoubleArray(ArrayRef value) { + return SetEntryValue(m_handle, Value::MakeDoubleArray(value)); +} + +inline bool NetworkTableEntry::SetStringArray(ArrayRef value) { + return SetEntryValue(m_handle, Value::MakeStringArray(value)); +} + +inline void NetworkTableEntry::ForceSetValue(std::shared_ptr value) { + SetEntryTypeValue(m_handle, value); +} + +inline void NetworkTableEntry::ForceSetBoolean(bool value) { + SetEntryTypeValue(m_handle, Value::MakeBoolean(value)); +} + +inline void NetworkTableEntry::ForceSetDouble(double value) { + SetEntryTypeValue(m_handle, Value::MakeDouble(value)); +} + +inline void NetworkTableEntry::ForceSetString(StringRef value) { + SetEntryTypeValue(m_handle, Value::MakeString(value)); +} + +inline void NetworkTableEntry::ForceSetRaw(StringRef value) { + SetEntryTypeValue(m_handle, Value::MakeRaw(value)); +} + +inline void NetworkTableEntry::ForceSetBooleanArray(ArrayRef value) { + SetEntryTypeValue(m_handle, Value::MakeBooleanArray(value)); +} + +inline void NetworkTableEntry::ForceSetDoubleArray(ArrayRef value) { + SetEntryTypeValue(m_handle, Value::MakeDoubleArray(value)); +} + +inline void NetworkTableEntry::ForceSetStringArray( + ArrayRef value) { + SetEntryTypeValue(m_handle, Value::MakeStringArray(value)); +} + +inline void NetworkTableEntry::SetFlags(unsigned int flags) { + SetEntryFlags(m_handle, GetFlags() | flags); +} + +inline void NetworkTableEntry::ClearFlags(unsigned int flags) { + SetEntryFlags(m_handle, GetFlags() & ~flags); +} + +inline void NetworkTableEntry::SetPersistent() { SetFlags(kPersistent); } + +inline void NetworkTableEntry::ClearPersistent() { ClearFlags(kPersistent); } + +inline bool NetworkTableEntry::IsPersistent() const { + return (GetFlags() & kPersistent) != 0; +} + +inline void NetworkTableEntry::Delete() { DeleteEntry(m_handle); } + +inline void NetworkTableEntry::CreateRpc( + std::function callback) { + ::nt::CreateRpc(m_handle, StringRef("\0", 1), callback); +} + +inline RpcCall NetworkTableEntry::CallRpc(StringRef params) { + return RpcCall{m_handle, ::nt::CallRpc(m_handle, params)}; +} + +inline NT_EntryListener NetworkTableEntry::AddListener( + std::function callback, + unsigned int flags) const { + return AddEntryListener(m_handle, callback, flags); +} + +inline void NetworkTableEntry::RemoveListener(NT_EntryListener entry_listener) { + RemoveEntryListener(entry_listener); +} + +} // namespace nt + +#endif // NT_ENTRY_INL_ diff --git a/src/main/native/include/networktables/NetworkTableInstance.h b/src/main/native/include/networktables/NetworkTableInstance.h new file mode 100644 index 0000000..03a30d1 --- /dev/null +++ b/src/main/native/include/networktables/NetworkTableInstance.h @@ -0,0 +1,521 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_INSTANCE_H_ +#define NT_INSTANCE_H_ + +#include +#include +#include +#include + +#include "llvm/ArrayRef.h" +#include "llvm/StringRef.h" + +#include "networktables/NetworkTable.h" +#include "networktables/NetworkTableEntry.h" +#include "ntcore_c.h" +#include "ntcore_cpp.h" + +#ifndef NT_NOEXCEPT +#ifdef _MSC_VER +#if _MSC_VER >= 1900 +#define NT_NOEXCEPT noexcept +#else +#define NT_NOEXCEPT throw() +#endif +#else +#define NT_NOEXCEPT noexcept +#endif +#endif + +namespace nt { + +using llvm::ArrayRef; +using llvm::StringRef; + +/** NetworkTables Instance. + * + * Instances are completely independent from each other. Table operations on + * one instance will not be visible to other instances unless the instances are + * connected via the network. The main limitation on instances is that you + * cannot have two servers on the same network port. The main utility of + * instances is for unit testing, but they can also enable one program to + * connect to two different NetworkTables networks. + * + * The global "default" instance (as returned by GetDefault()) is + * always available, and is intended for the common case when there is only + * a single NetworkTables instance being used in the program. The + * default instance cannot be destroyed. + * + * Additional instances can be created with the Create() function. + * Instances are not reference counted or RAII. Instead, they must be + * explicitly destroyed (with Destroy()). + */ +class NetworkTableInstance final { + public: + /** + * Client/server mode flag values (as returned by GetNetworkMode()). + * This is a bitmask. + */ + enum NetworkMode { + kNetModeNone = NT_NET_MODE_NONE, + kNetModeServer = NT_NET_MODE_SERVER, + kNetModeClient = NT_NET_MODE_CLIENT, + kNetModeStarting = NT_NET_MODE_STARTING, + kNetModeFailure = NT_NET_MODE_FAILURE + }; + + /** + * Logging levels (as used by SetLogger()). + */ + enum LogLevel { + kLogCritical = NT_LOG_CRITICAL, + kLogError = NT_LOG_ERROR, + kLogWarning = NT_LOG_WARNING, + kLogInfo = NT_LOG_INFO, + kLogDebug = NT_LOG_DEBUG, + kLogDebug1 = NT_LOG_DEBUG1, + kLogDebug2 = NT_LOG_DEBUG2, + kLogDebug3 = NT_LOG_DEBUG3, + kLogDebug4 = NT_LOG_DEBUG4 + }; + + /** + * The default port that network tables operates on. + */ + enum { kDefaultPort = NT_DEFAULT_PORT }; + + /** + * Construct invalid instance. + */ + NetworkTableInstance() NT_NOEXCEPT; + + /** + * Construct from native handle. + * @param handle Native handle + */ + explicit NetworkTableInstance(NT_Inst inst) NT_NOEXCEPT; + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + explicit operator bool() const { return m_handle != 0; } + + /** + * Get global default instance. + * @return Global default instance + */ + static NetworkTableInstance GetDefault(); + + /** + * Create an instance. + * @return Newly created instance + */ + static NetworkTableInstance Create(); + + /** + * Destroys an instance (note: this has global effect). + * @param inst Instance + */ + static void Destroy(NetworkTableInstance inst); + + /** + * Gets the native handle for the entry. + * @return Native handle + */ + NT_Inst GetHandle() const; + + /** + * Gets the entry for a key. + * @param name Key + * @return Network table entry. + */ + NetworkTableEntry GetEntry(StringRef name); + + /** + * Get entries starting with the given prefix. + * The results are optionally filtered by string prefix and entry type to + * only return a subset of all entries. + * + * @param prefix entry name required prefix; only entries whose name + * starts with this string are returned + * @param types bitmask of types; 0 is treated as a "don't care" + * @return Array of entries. + */ + std::vector GetEntries(StringRef prefix, + unsigned int types); + + /** + * Get information about entries starting with the given prefix. + * The results are optionally filtered by string prefix and entry type to + * only return a subset of all entries. + * + * @param prefix entry name required prefix; only entries whose name + * starts with this string are returned + * @param types bitmask of types; 0 is treated as a "don't care" + * @return Array of entry information. + */ + std::vector GetEntryInfo(StringRef prefix, + unsigned int types) const; + + /** + * Gets the table with the specified key. + * + * @param key the key name + * @return The network table + */ + std::shared_ptr GetTable(StringRef key) const; + + /** + * Deletes ALL keys in ALL subtables (except persistent values). + * Use with caution! + */ + void DeleteAllEntries(); + + /** + * @defgroup EntryListenerFunctions Entry Listener Functions + * @{ + */ + + /** + * Add a listener for all entries starting with a certain prefix. + * + * @param prefix UTF-8 string prefix + * @param callback listener to add + * @param flags EntryListenerFlags bitmask + * @return Listener handle + */ + NT_EntryListener AddEntryListener( + StringRef prefix, + std::function callback, + unsigned int flags) const; + + /** + * Remove an entry listener. + * @param entry_listener Listener handle to remove + */ + static void RemoveEntryListener(NT_EntryListener entry_listener); + + /** + * Wait for the entry listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the entry listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + bool WaitForEntryListenerQueue(double timeout); + + /** @} */ + + /** + * @defgroup ConnectionListenerFunctions Connection Listener Functions + * @{ + */ + + /** + * Add a connection listener. + * + * @param callback listener to add + * @param immediate_notify notify listener of all existing connections + * @return Listener handle + */ + NT_ConnectionListener AddConnectionListener( + std::function callback, + bool immediate_notify) const; + + /** + * Remove a connection listener. + * @param conn_listener Listener handle to remove + */ + static void RemoveConnectionListener(NT_ConnectionListener conn_listener); + + /** + * Wait for the connection listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the connection listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + bool WaitForConnectionListenerQueue(double timeout); + + /** @} */ + + /** + * @defgroup RpcFunctions Remote Procedure Call Functions + * @{ + */ + + /** + * Wait for the incoming RPC call queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the RPC call + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + bool WaitForRpcCallQueue(double timeout); + + /** @} */ + + /** + * @defgroup NetworkFunctions Client/Server Functions + * @{ + */ + + /** + * Set the network identity of this node. + * This is the name used during the initial connection handshake, and is + * visible through ConnectionInfo on the remote node. + * @param name identity to advertise + */ + void SetNetworkIdentity(StringRef name); + + /** + * Get the current network mode. + * @return Bitmask of NetworkMode. + */ + unsigned int GetNetworkMode() const; + + /** + * Starts a server using the specified filename, listening address, and port. + * + * @param persist_filename the name of the persist file to use (UTF-8 string, + * null terminated) + * @param listen_address the address to listen on, or null to listen on any + * address (UTF-8 string, null terminated) + * @param port port to communicate over + */ + void StartServer(StringRef persist_filename = "networktables.ini", + const char* listen_address = "", + unsigned int port = kDefaultPort); + + /** + * Stops the server if it is running. + */ + void StopServer(); + + /** + * Starts a client. Use SetServer to set the server name and port. + */ + void StartClient(); + + /** + * Starts a client using the specified server and port + * + * @param server_name server name (UTF-8 string, null terminated) + * @param port port to communicate over + */ + void StartClient(const char* server_name, unsigned int port = kDefaultPort); + + /** + * Starts a client using the specified (server, port) combinations. The + * client will attempt to connect to each server in round robin fashion. + * + * @param servers array of server name and port pairs + */ + void StartClient(ArrayRef> servers); + + /** + * Starts a client using the specified servers and port. The + * client will attempt to connect to each server in round robin fashion. + * + * @param servers array of server names + * @param port port to communicate over + */ + void StartClient(ArrayRef servers, + unsigned int port = kDefaultPort); + + /** + * Starts a client using commonly known robot addresses for the specified + * team. + * + * @param team team number + * @param port port to communicate over + */ + void StartClientTeam(unsigned int team, unsigned int port = kDefaultPort); + + /** + * Stops the client if it is running. + */ + void StopClient(); + + /** + * Sets server address and port for client (without restarting client). + * + * @param server_name server name (UTF-8 string, null terminated) + * @param port port to communicate over + */ + void SetServer(const char* server_name, unsigned int port = kDefaultPort); + + /** + * Sets server addresses and ports for client (without restarting client). + * The client will attempt to connect to each server in round robin fashion. + * + * @param servers array of server name and port pairs + */ + void SetServer(ArrayRef> servers); + + /** + * Sets server addresses and port for client (without restarting client). + * The client will attempt to connect to each server in round robin fashion. + * + * @param servers array of server names + * @param port port to communicate over + */ + void SetServer(ArrayRef servers, unsigned int port = kDefaultPort); + + /** + * Sets server addresses and port for client (without restarting client). + * Connects using commonly known robot addresses for the specified team. + * + * @param team team number + * @param port port to communicate over + */ + void SetServerTeam(unsigned int team, unsigned int port = kDefaultPort); + + /** + * Starts requesting server address from Driver Station. + * This connects to the Driver Station running on localhost to obtain the + * server IP address. + * + * @param port server port to use in combination with IP from DS + */ + void StartDSClient(unsigned int port = kDefaultPort); + + /** + * Stops requesting server address from Driver Station. + */ + void StopDSClient(); + + /** + * Set the periodic update rate. + * Sets how frequently updates are sent to other nodes over the network. + * + * @param interval update interval in seconds (range 0.01 to 1.0) + */ + void SetUpdateRate(double interval); + + /** + * Flushes all updated values immediately to the network. + * @note This is rate-limited to protect the network from flooding. + * This is primarily useful for synchronizing network updates with + * user code. + */ + void Flush() const; + + /** + * Get information on the currently established network connections. + * If operating as a client, this will return either zero or one values. + * @return array of connection information + */ + std::vector GetConnections() const; + + /** + * Return whether or not the instance is connected to another node. + * @return True if connected. + */ + bool IsConnected() const; + + /** @} */ + + /** + * @defgroup PersistentFunctions Persistent Functions + * @{ + */ + + /** + * Save persistent values to a file. The server automatically does this, + * but this function provides a way to save persistent values in the same + * format to a file on either a client or a server. + * @param filename filename + * @return error string, or nullptr if successful + */ + const char* SavePersistent(StringRef filename) const; + + /** + * Load persistent values from a file. The server automatically does this + * at startup, but this function provides a way to restore persistent values + * in the same format from a file at any time on either a client or a server. + * @param filename filename + * @param warn callback function for warnings + * @return error string, or nullptr if successful + */ + const char* LoadPersistent( + StringRef filename, + std::function warn); + + /** @} */ + + /** + * @defgroup LoggerFunctions Logger Functions + * @{ + */ + + /** + * Add logger callback function. By default, log messages are sent to stderr; + * this function sends log messages with the specified levels to the provided + * callback function instead. The callback function will only be called for + * log messages with level greater than or equal to minLevel and less than or + * equal to maxLevel; messages outside this range will be silently ignored. + * + * @param func log callback function + * @param minLevel minimum log level + * @param maxLevel maximum log level + * @return Logger handle + */ + NT_Logger AddLogger(std::function func, + unsigned int min_level, unsigned int max_level); + + /** + * Remove a logger. + * @param logger Logger handle to remove + */ + static void RemoveLogger(NT_Logger logger); + + /** + * Wait for the incoming log event queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the log event + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ + bool WaitForLoggerQueue(double timeout); + + /** @} */ + + /** + * Equality operator. Returns true if both instances refer to the same + * native handle. + */ + bool operator==(const NetworkTableInstance& other) const { + return m_handle == other.m_handle; + } + + /** Inequality operator. */ + bool operator!=(const NetworkTableInstance& other) const { + return !(*this == other); + } + + private: + /* Native handle */ + NT_Inst m_handle; +}; + +} // namespace nt + +#include "networktables/NetworkTableInstance.inl" + +#endif // NT_INSTANCE_H_ diff --git a/src/main/native/include/networktables/NetworkTableInstance.inl b/src/main/native/include/networktables/NetworkTableInstance.inl new file mode 100644 index 0000000..09c1de6 --- /dev/null +++ b/src/main/native/include/networktables/NetworkTableInstance.inl @@ -0,0 +1,176 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_INSTANCE_INL_ +#define NT_INSTANCE_INL_ + +namespace nt { + +inline NetworkTableInstance::NetworkTableInstance() NT_NOEXCEPT : m_handle{0} {} + +inline NetworkTableInstance::NetworkTableInstance(NT_Inst handle) NT_NOEXCEPT + : m_handle{handle} {} + +inline NetworkTableInstance NetworkTableInstance::GetDefault() { + return NetworkTableInstance{GetDefaultInstance()}; +} + +inline NetworkTableInstance NetworkTableInstance::Create() { + return NetworkTableInstance{CreateInstance()}; +} + +inline void NetworkTableInstance::Destroy(NetworkTableInstance inst) { + if (inst.m_handle != 0) DestroyInstance(inst.m_handle); +} + +inline NT_Inst NetworkTableInstance::GetHandle() const { return m_handle; } + +inline NetworkTableEntry NetworkTableInstance::GetEntry(StringRef name) { + return NetworkTableEntry{::nt::GetEntry(m_handle, name)}; +} + +inline std::vector NetworkTableInstance::GetEntries( + StringRef prefix, unsigned int types) { + std::vector entries; + for (auto entry : ::nt::GetEntries(m_handle, prefix, types)) + entries.emplace_back(entry); + return entries; +} + +inline std::vector NetworkTableInstance::GetEntryInfo( + StringRef prefix, unsigned int types) const { + return ::nt::GetEntryInfo(m_handle, prefix, types); +} + +inline void NetworkTableInstance::DeleteAllEntries() { + ::nt::DeleteAllEntries(m_handle); +} + +inline void NetworkTableInstance::RemoveEntryListener( + NT_EntryListener entry_listener) { + ::nt::RemoveEntryListener(entry_listener); +} + +inline bool NetworkTableInstance::WaitForEntryListenerQueue(double timeout) { + return ::nt::WaitForEntryListenerQueue(m_handle, timeout); +} + +inline void NetworkTableInstance::RemoveConnectionListener( + NT_ConnectionListener conn_listener) { + ::nt::RemoveConnectionListener(conn_listener); +} + +inline bool NetworkTableInstance::WaitForConnectionListenerQueue( + double timeout) { + return ::nt::WaitForConnectionListenerQueue(m_handle, timeout); +} + +inline bool NetworkTableInstance::WaitForRpcCallQueue(double timeout) { + return ::nt::WaitForRpcCallQueue(m_handle, timeout); +} + +inline void NetworkTableInstance::SetNetworkIdentity(StringRef name) { + ::nt::SetNetworkIdentity(m_handle, name); +} + +inline unsigned int NetworkTableInstance::GetNetworkMode() const { + return ::nt::GetNetworkMode(m_handle); +} + +inline void NetworkTableInstance::StartServer(StringRef persist_filename, + const char* listen_address, + unsigned int port) { + ::nt::StartServer(m_handle, persist_filename, listen_address, port); +} + +inline void NetworkTableInstance::StopServer() { ::nt::StopServer(m_handle); } + +inline void NetworkTableInstance::StartClient() { ::nt::StartClient(m_handle); } + +inline void NetworkTableInstance::StartClient(const char* server_name, + unsigned int port) { + ::nt::StartClient(m_handle, server_name, port); +} + +inline void NetworkTableInstance::StartClient( + ArrayRef> servers) { + ::nt::StartClient(m_handle, servers); +} + +inline void NetworkTableInstance::StartClientTeam(unsigned int team, + unsigned int port) { + ::nt::StartClientTeam(m_handle, team, port); +} + +inline void NetworkTableInstance::StopClient() { ::nt::StopClient(m_handle); } + +inline void NetworkTableInstance::SetServer(const char* server_name, + unsigned int port) { + ::nt::SetServer(m_handle, server_name, port); +} + +inline void NetworkTableInstance::SetServer( + ArrayRef> servers) { + ::nt::SetServer(m_handle, servers); +} + +inline void NetworkTableInstance::SetServerTeam(unsigned int team, + unsigned int port) { + ::nt::SetServerTeam(m_handle, team, port); +} + +inline void NetworkTableInstance::StartDSClient(unsigned int port) { + ::nt::StartDSClient(m_handle, port); +} + +inline void NetworkTableInstance::StopDSClient() { + ::nt::StopDSClient(m_handle); +} + +inline void NetworkTableInstance::SetUpdateRate(double interval) { + ::nt::SetUpdateRate(m_handle, interval); +} + +inline void NetworkTableInstance::Flush() const { ::nt::Flush(m_handle); } + +inline std::vector NetworkTableInstance::GetConnections() + const { + return ::nt::GetConnections(m_handle); +} + +inline bool NetworkTableInstance::IsConnected() const { + return ::nt::IsConnected(m_handle); +} + +inline const char* NetworkTableInstance::SavePersistent( + StringRef filename) const { + return ::nt::SavePersistent(m_handle, filename); +} + +inline const char* NetworkTableInstance::LoadPersistent( + StringRef filename, + std::function warn) { + return ::nt::LoadPersistent(m_handle, filename, warn); +} + +inline NT_Logger NetworkTableInstance::AddLogger( + std::function func, unsigned int min_level, + unsigned int max_level) { + return ::nt::AddLogger(m_handle, func, min_level, max_level); +} + +inline void NetworkTableInstance::RemoveLogger(NT_Logger logger) { + ::nt::RemoveLogger(logger); +} + +inline bool NetworkTableInstance::WaitForLoggerQueue(double timeout) { + return ::nt::WaitForLoggerQueue(m_handle, timeout); +} + +} // namespace nt + +#endif // NT_INSTANCE_INL_ diff --git a/src/main/native/include/networktables/NetworkTableType.h b/src/main/native/include/networktables/NetworkTableType.h new file mode 100644 index 0000000..62b9b58 --- /dev/null +++ b/src/main/native/include/networktables/NetworkTableType.h @@ -0,0 +1,29 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TYPE_H_ +#define NT_TYPE_H_ + +#include "ntcore_c.h" + +namespace nt { + +enum class NetworkTableType { + kUnassigned = NT_UNASSIGNED, + kBoolean = NT_BOOLEAN, + kDouble = NT_DOUBLE, + kString = NT_STRING, + kRaw = NT_RAW, + kBooleanArray = NT_BOOLEAN_ARRAY, + kDoubleArray = NT_DOUBLE_ARRAY, + kStringArray = NT_STRING_ARRAY, + kRpc = NT_RPC +}; + +} // namespace nt + +#endif // NT_TYPE_H_ diff --git a/src/main/native/include/networktables/NetworkTableValue.h b/src/main/native/include/networktables/NetworkTableValue.h new file mode 100644 index 0000000..cff1a34 --- /dev/null +++ b/src/main/native/include/networktables/NetworkTableValue.h @@ -0,0 +1,404 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2015. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_VALUE_H_ +#define NT_VALUE_H_ + +#include +#include +#include +#include +#include + +#include "llvm/ArrayRef.h" +#include "llvm/StringRef.h" + +#include "ntcore_c.h" + +namespace nt { + +using llvm::ArrayRef; +using llvm::StringRef; + +/** + * A network table entry value. + */ +class Value final { + struct private_init {}; + + public: + Value(); + Value(NT_Type type, unsigned long long time, const private_init&); + ~Value(); + + /** + * Get the data type. + * @return The type. + */ + NT_Type type() const { return m_val.type; } + + /** + * Get the data value stored. + * @return The type. + */ + const NT_Value& value() const { return m_val; } + + /** + * Get the creation time of the value. + * @return The time, in the units returned by nt::Now(). + */ + unsigned long long last_change() const { return m_val.last_change; } + + /** + * Get the creation time of the value. + * @return The time, in the units returned by nt::Now(). + */ + unsigned long long time() const { return m_val.last_change; } + + /** + * @defgroup TypeCheckers Type Checkers + * @{ + */ + + /** + * Determine if entry value contains a value or is unassigned. + * @return True if the entry value contains a value. + */ + bool IsValid() const { return m_val.type != NT_UNASSIGNED; } + + /** + * Determine if entry value contains a boolean. + * @return True if the entry value is of boolean type. + */ + bool IsBoolean() const { return m_val.type == NT_BOOLEAN; } + + /** + * Determine if entry value contains a double. + * @return True if the entry value is of double type. + */ + bool IsDouble() const { return m_val.type == NT_DOUBLE; } + + /** + * Determine if entry value contains a string. + * @return True if the entry value is of string type. + */ + bool IsString() const { return m_val.type == NT_STRING; } + + /** + * Determine if entry value contains a raw. + * @return True if the entry value is of raw type. + */ + bool IsRaw() const { return m_val.type == NT_RAW; } + + /** + * Determine if entry value contains a rpc definition. + * @return True if the entry value is of rpc definition type. + */ + bool IsRpc() const { return m_val.type == NT_RPC; } + + /** + * Determine if entry value contains a boolean array. + * @return True if the entry value is of boolean array type. + */ + bool IsBooleanArray() const { return m_val.type == NT_BOOLEAN_ARRAY; } + + /** + * Determine if entry value contains a double array. + * @return True if the entry value is of double array type. + */ + bool IsDoubleArray() const { return m_val.type == NT_DOUBLE_ARRAY; } + + /** + * Determine if entry value contains a string array. + * @return True if the entry value is of string array type. + */ + bool IsStringArray() const { return m_val.type == NT_STRING_ARRAY; } + + /** @} */ + + /** + * @defgroup TypeSafeGetters Type-Safe Getters + * @{ + */ + + /** + * Get the entry's boolean value. + * @return The boolean value. + */ + bool GetBoolean() const { + assert(m_val.type == NT_BOOLEAN); + return m_val.data.v_boolean != 0; + } + + /** + * Get the entry's double value. + * @return The double value. + */ + double GetDouble() const { + assert(m_val.type == NT_DOUBLE); + return m_val.data.v_double; + } + + /** + * Get the entry's string value. + * @return The string value. + */ + StringRef GetString() const { + assert(m_val.type == NT_STRING); + return m_string; + } + + /** + * Get the entry's raw value. + * @return The raw value. + */ + StringRef GetRaw() const { + assert(m_val.type == NT_RAW); + return m_string; + } + + /** + * Get the entry's rpc definition value. + * @return The rpc definition value. + */ + StringRef GetRpc() const { + assert(m_val.type == NT_RPC); + return m_string; + } + + /** + * Get the entry's boolean array value. + * @return The boolean array value. + */ + ArrayRef GetBooleanArray() const { + assert(m_val.type == NT_BOOLEAN_ARRAY); + return ArrayRef(m_val.data.arr_boolean.arr, + m_val.data.arr_boolean.size); + } + + /** + * Get the entry's double array value. + * @return The double array value. + */ + ArrayRef GetDoubleArray() const { + assert(m_val.type == NT_DOUBLE_ARRAY); + return ArrayRef(m_val.data.arr_double.arr, + m_val.data.arr_double.size); + } + + /** + * Get the entry's string array value. + * @return The string array value. + */ + ArrayRef GetStringArray() const { + assert(m_val.type == NT_STRING_ARRAY); + return m_string_array; + } + + /** @} */ + + /** + * @defgroup Factories Factory functions + * @{ + */ + + /** + * Creates a boolean entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeBoolean(bool value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_BOOLEAN, time, private_init()); + val->m_val.data.v_boolean = value; + return val; + } + + /** + * Creates a double entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeDouble(double value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_DOUBLE, time, private_init()); + val->m_val.data.v_double = value; + return val; + } + + /** + * Creates a string entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeString(StringRef value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_STRING, time, private_init()); + val->m_string = value; + val->m_val.data.v_string.str = const_cast(val->m_string.c_str()); + val->m_val.data.v_string.len = val->m_string.size(); + return val; + } + + /** + * Creates a string entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ +#ifdef _MSC_VER + template >> +#else + template ::value>::type> +#endif + static std::shared_ptr MakeString(T&& value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_STRING, time, private_init()); + val->m_string = std::move(value); + val->m_val.data.v_string.str = const_cast(val->m_string.c_str()); + val->m_val.data.v_string.len = val->m_string.size(); + return val; + } + + /** + * Creates a raw entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeRaw(StringRef value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_RAW, time, private_init()); + val->m_string = value; + val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); + val->m_val.data.v_raw.len = val->m_string.size(); + return val; + } + + /** + * Creates a raw entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ +#ifdef _MSC_VER + template >> +#else + template ::value>::type> +#endif + static std::shared_ptr MakeRaw(T&& value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_RAW, time, private_init()); + val->m_string = std::move(value); + val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); + val->m_val.data.v_raw.len = val->m_string.size(); + return val; + } + + /** + * Creates a rpc entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeRpc(StringRef value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_RPC, time, private_init()); + val->m_string = value; + val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); + val->m_val.data.v_raw.len = val->m_string.size(); + return val; + } + + /** + * Creates a rpc entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + template + static std::shared_ptr MakeRpc(T&& value, + unsigned long long time = 0) { + auto val = std::make_shared(NT_RPC, time, private_init()); + val->m_string = std::move(value); + val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); + val->m_val.data.v_raw.len = val->m_string.size(); + return val; + } + + /** + * Creates a boolean array entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeBooleanArray(ArrayRef value, + unsigned long long time = 0); + + /** + * Creates a double array entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeDoubleArray(ArrayRef value, + unsigned long long time = 0); + + /** + * Creates a string array entry value. + * @param value the value + * @param time if nonzero, the creation time to use (instead of the current + * time) + * @return The entry value + */ + static std::shared_ptr MakeStringArray(ArrayRef value, + unsigned long long time = 0); + + // Note: This function moves the values out of the vector. + static std::shared_ptr MakeStringArray( + std::vector&& value, unsigned long long time = 0); + + /** @} */ + + Value(const Value&) = delete; + Value& operator=(const Value&) = delete; + friend bool operator==(const Value& lhs, const Value& rhs); + + private: + NT_Value m_val; + std::string m_string; + std::vector m_string_array; +}; + +bool operator==(const Value& lhs, const Value& rhs); +inline bool operator!=(const Value& lhs, const Value& rhs) { + return !(lhs == rhs); +} + +/** NetworkTable Value alias for similarity with Java. */ +typedef Value NetworkTableValue; + +} // namespace nt + +#endif // NT_VALUE_H_ diff --git a/src/main/native/include/networktables/RpcCall.h b/src/main/native/include/networktables/RpcCall.h new file mode 100644 index 0000000..d189306 --- /dev/null +++ b/src/main/native/include/networktables/RpcCall.h @@ -0,0 +1,101 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_RPCCALL_H_ +#define NT_RPCCALL_H_ + +#include +#include + +#include "ntcore_c.h" + +namespace nt { + +class NetworkTableEntry; + +/** NetworkTables Remote Procedure Call */ +class RpcCall final { + public: + /** + * Construct invalid instance. + */ + RpcCall() : m_entry(0), m_call(0) {} + + /** + * Construct from native handles. + * @param entry Entry handle + * @param call Call handle + */ + RpcCall(NT_Entry entry, NT_RpcCall call) + : m_entry(entry), m_call(call) {} + + RpcCall(RpcCall&& other); + RpcCall(const RpcCall&) = delete; + RpcCall& operator=(const RpcCall&) = delete; + + /** + * Destructor. Cancels the result if no other action taken. + */ + ~RpcCall(); + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + explicit operator bool() const { return m_call != 0; } + + /** + * Get the RPC entry. + * @return NetworkTableEntry for the RPC. + */ + NetworkTableEntry GetEntry() const; + + /** + * Get the call native handle. + * @return Native handle. + */ + NT_RpcCall GetCall() const { return m_call; } + + /** + * Get the result (return value). This function blocks until + * the result is received. + * @param result received result (output) + * @return False on error, true otherwise. + */ + bool GetResult(std::string* result); + + /** + * Get the result (return value). This function blocks until + * the result is received or it times out. + * @param result received result (output) + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return False on error or timeout, true otherwise. + */ + bool GetResult(std::string* result, double timeout, bool* timed_out); + + /** + * Ignore the result. This function is non-blocking. + */ + void CancelResult(); + + friend void swap(RpcCall& first, RpcCall& second) { + using std::swap; + swap(first.m_entry, second.m_entry); + swap(first.m_call, second.m_call); + } + + private: + NT_Entry m_entry; + NT_RpcCall m_call; +}; + +} // namespace nt + +#include "networktables/RpcCall.inl" + +#endif // NT_RPCCALL_H_ diff --git a/src/main/native/include/networktables/RpcCall.inl b/src/main/native/include/networktables/RpcCall.inl new file mode 100644 index 0000000..d7dacf5 --- /dev/null +++ b/src/main/native/include/networktables/RpcCall.inl @@ -0,0 +1,48 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_RPCCALL_INL_ +#define NT_RPCCALL_INL_ + +#include "ntcore_cpp.h" + +namespace nt { + +inline RpcCall::RpcCall(RpcCall&& other) : RpcCall() { + swap(*this, other); +} + +inline RpcCall::~RpcCall() { + // automatically cancel result if user didn't request it + if (m_call != 0) CancelResult(); +} + +inline bool RpcCall::GetResult(std::string* result) { + if (GetRpcResult(m_entry, m_call, result)) { + m_call = 0; + return true; + } + return false; +} + +inline bool RpcCall::GetResult(std::string* result, double timeout, + bool* timed_out) { + if (GetRpcResult(m_entry, m_call, result, timeout, timed_out)) { + m_call = 0; + return true; + } + return false; +} + +inline void RpcCall::CancelResult() { + CancelRpcResult(m_entry, m_call); + m_call = 0; +} + +} // namespace nt + +#endif // NT_RPCCALL_INL_ diff --git a/src/main/native/include/networktables/TableEntryListener.h b/src/main/native/include/networktables/TableEntryListener.h new file mode 100644 index 0000000..3aac451 --- /dev/null +++ b/src/main/native/include/networktables/TableEntryListener.h @@ -0,0 +1,43 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TABLEENTRYLISTENER_H_ +#define NT_TABLEENTRYLISTENER_H_ + +#include +#include + +#include "llvm/StringRef.h" + +namespace nt { + +class NetworkTable; +class NetworkTableEntry; +class Value; + +using llvm::StringRef; + +/** + * A listener that listens to changes in values in a NetworkTable. + * + * Called when a key-value pair is changed in a NetworkTable. + * + * @param table the table the key-value pair exists in + * @param key the key associated with the value that changed + * @param entry the entry associated with the value that changed + * @param value the new value + * @param flags update flags; for example, EntryListenerFlags.kNew if the key + * did not previously exist + */ +typedef std::function value, int flags)> + TableEntryListener; + +} // namespace nt + +#endif // NT_TABLEENTRYLISTENER_H_ diff --git a/src/main/native/include/networktables/TableListener.h b/src/main/native/include/networktables/TableListener.h new file mode 100644 index 0000000..723549d --- /dev/null +++ b/src/main/native/include/networktables/TableListener.h @@ -0,0 +1,37 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TABLELISTENER_H_ +#define NT_TABLELISTENER_H_ + +#include +#include + +#include "llvm/StringRef.h" + +namespace nt { + +class NetworkTable; + +using llvm::StringRef; + +/** + * A listener that listens to new sub-tables in a NetworkTable. + * + * Called when a new table is created. + * + * @param parent the parent of the table + * @param name the name of the new table + * @param table the new table + */ +typedef std::function + TableListener; + +} // namespace nt + +#endif // NT_TABLELISTENER_H_ diff --git a/src/main/native/include/nt_Value.h b/src/main/native/include/nt_Value.h deleted file mode 100644 index a5c1304..0000000 --- a/src/main/native/include/nt_Value.h +++ /dev/null @@ -1,183 +0,0 @@ -/*----------------------------------------------------------------------------*/ -/* Copyright (c) FIRST 2015. All Rights Reserved. */ -/* Open Source Software - may be modified and shared by FRC teams. The code */ -/* must be accompanied by the FIRST BSD license file in the root directory of */ -/* the project. */ -/*----------------------------------------------------------------------------*/ - -#ifndef NT_VALUE_H_ -#define NT_VALUE_H_ - -#include -#include -#include -#include -#include - -#include "llvm/ArrayRef.h" -#include "llvm/StringRef.h" - -#include "ntcore_c.h" - -namespace nt { - -using llvm::ArrayRef; -using llvm::StringRef; - -/** NetworkTables Entry Value */ -class Value { - struct private_init {}; - - public: - Value(); - Value(NT_Type type, const private_init&); - ~Value(); - - NT_Type type() const { return m_val.type; } - const NT_Value& value() const { return m_val; } - unsigned long long last_change() const { return m_val.last_change; } - - /* - * Type Checkers - */ - bool IsBoolean() const { return m_val.type == NT_BOOLEAN; } - bool IsDouble() const { return m_val.type == NT_DOUBLE; } - bool IsString() const { return m_val.type == NT_STRING; } - bool IsRaw() const { return m_val.type == NT_RAW; } - bool IsRpc() const { return m_val.type == NT_RPC; } - bool IsBooleanArray() const { return m_val.type == NT_BOOLEAN_ARRAY; } - bool IsDoubleArray() const { return m_val.type == NT_DOUBLE_ARRAY; } - bool IsStringArray() const { return m_val.type == NT_STRING_ARRAY; } - - /* - * Type-Safe Getters - */ - bool GetBoolean() const { - assert(m_val.type == NT_BOOLEAN); - return m_val.data.v_boolean != 0; - } - double GetDouble() const { - assert(m_val.type == NT_DOUBLE); - return m_val.data.v_double; - } - StringRef GetString() const { - assert(m_val.type == NT_STRING); - return m_string; - } - StringRef GetRaw() const { - assert(m_val.type == NT_RAW); - return m_string; - } - StringRef GetRpc() const { - assert(m_val.type == NT_RPC); - return m_string; - } - ArrayRef GetBooleanArray() const { - assert(m_val.type == NT_BOOLEAN_ARRAY); - return ArrayRef(m_val.data.arr_boolean.arr, - m_val.data.arr_boolean.size); - } - ArrayRef GetDoubleArray() const { - assert(m_val.type == NT_DOUBLE_ARRAY); - return ArrayRef(m_val.data.arr_double.arr, - m_val.data.arr_double.size); - } - ArrayRef GetStringArray() const { - assert(m_val.type == NT_STRING_ARRAY); - return m_string_array; - } - - static std::shared_ptr MakeBoolean(bool value) { - auto val = std::make_shared(NT_BOOLEAN, private_init()); - val->m_val.data.v_boolean = value; - return val; - } - static std::shared_ptr MakeDouble(double value) { - auto val = std::make_shared(NT_DOUBLE, private_init()); - val->m_val.data.v_double = value; - return val; - } - static std::shared_ptr MakeString(StringRef value) { - auto val = std::make_shared(NT_STRING, private_init()); - val->m_string = value; - val->m_val.data.v_string.str = const_cast(val->m_string.c_str()); - val->m_val.data.v_string.len = val->m_string.size(); - return val; - } -#ifdef _MSC_VER - template >> -#else - template ::value>::type> -#endif - static std::shared_ptr MakeString(T&& value) { - auto val = std::make_shared(NT_STRING, private_init()); - val->m_string = std::move(value); - val->m_val.data.v_string.str = const_cast(val->m_string.c_str()); - val->m_val.data.v_string.len = val->m_string.size(); - return val; - } - static std::shared_ptr MakeRaw(StringRef value) { - auto val = std::make_shared(NT_RAW, private_init()); - val->m_string = value; - val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); - val->m_val.data.v_raw.len = val->m_string.size(); - return val; - } -#ifdef _MSC_VER - template >> -#else - template ::value>::type> -#endif - static std::shared_ptr MakeRaw(T&& value) { - auto val = std::make_shared(NT_RAW, private_init()); - val->m_string = std::move(value); - val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); - val->m_val.data.v_raw.len = val->m_string.size(); - return val; - } - static std::shared_ptr MakeRpc(StringRef value) { - auto val = std::make_shared(NT_RPC, private_init()); - val->m_string = value; - val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); - val->m_val.data.v_raw.len = val->m_string.size(); - return val; - } - template - static std::shared_ptr MakeRpc(T&& value) { - auto val = std::make_shared(NT_RPC, private_init()); - val->m_string = std::move(value); - val->m_val.data.v_raw.str = const_cast(val->m_string.c_str()); - val->m_val.data.v_raw.len = val->m_string.size(); - return val; - } - - static std::shared_ptr MakeBooleanArray(ArrayRef value); - static std::shared_ptr MakeDoubleArray(ArrayRef value); - static std::shared_ptr MakeStringArray(ArrayRef value); - - // Note: This function moves the values out of the vector. - static std::shared_ptr MakeStringArray( - std::vector&& value); - - Value(const Value&) = delete; - Value& operator=(const Value&) = delete; - friend bool operator==(const Value& lhs, const Value& rhs); - - private: - NT_Value m_val; - std::string m_string; - std::vector m_string_array; -}; - -bool operator==(const Value& lhs, const Value& rhs); -inline bool operator!=(const Value& lhs, const Value& rhs) { - return !(lhs == rhs); -} - -} // namespace nt - -#endif // NT_VALUE_H_ diff --git a/src/main/native/include/ntcore_c.h b/src/main/native/include/ntcore_c.h index 3ea3b74..a4b9456 100644 --- a/src/main/native/include/ntcore_c.h +++ b/src/main/native/include/ntcore_c.h @@ -10,10 +10,27 @@ #include +#include "support/deprecated.h" + #ifdef __cplusplus extern "C" { #endif +/** Typedefs */ +typedef int NT_Bool; + +typedef unsigned int NT_Handle; +typedef NT_Handle NT_ConnectionListener; +typedef NT_Handle NT_ConnectionListenerPoller; +typedef NT_Handle NT_Entry; +typedef NT_Handle NT_EntryListener; +typedef NT_Handle NT_EntryListenerPoller; +typedef NT_Handle NT_Inst; +typedef NT_Handle NT_Logger; +typedef NT_Handle NT_LoggerPoller; +typedef NT_Handle NT_RpcCall; +typedef NT_Handle NT_RpcCallPoller; + /** Default network tables port number */ #define NT_DEFAULT_PORT 1735 @@ -46,7 +63,7 @@ enum NT_LogLevel { NT_LOG_DEBUG4 = 6 }; -/** NetworkTables notififier kinds. */ +/** NetworkTables notifier kinds. */ enum NT_NotifyKind { NT_NOTIFY_NONE = 0, NT_NOTIFY_IMMEDIATE = 0x01, /* initial listener addition */ @@ -59,11 +76,11 @@ enum NT_NotifyKind { /** Client/server modes */ enum NT_NetworkMode { - NT_NET_MODE_NONE = 0x00, /* not running */ - NT_NET_MODE_SERVER = 0x01, /* running in server mode */ - NT_NET_MODE_CLIENT = 0x02, /* running in client mode */ - NT_NET_MODE_STARTING = 0x04, /* flag for starting (either client or server) */ - NT_NET_MODE_FAILURE = 0x08, /* flag for failure (either client or server) */ + NT_NET_MODE_NONE = 0x00, /* not running */ + NT_NET_MODE_SERVER = 0x01, /* running in server mode */ + NT_NET_MODE_CLIENT = 0x02, /* running in client mode */ + NT_NET_MODE_STARTING = 0x04, /* flag for starting (either client or server) */ + NT_NET_MODE_FAILURE = 0x08, /* flag for failure (either client or server) */ }; /* @@ -72,14 +89,16 @@ enum NT_NetworkMode { /** A NetworkTables string. */ struct NT_String { - /** String contents (UTF-8). + /** + * String contents (UTF-8). * The string is NOT required to be zero-terminated. * When returned by the library, this is zero-terminated and allocated with * malloc(). */ char* str; - /** Length of the string in bytes. If the string happens to be zero + /** + * Length of the string in bytes. If the string happens to be zero * terminated, this does not include the zero-termination. */ size_t len; @@ -90,12 +109,12 @@ struct NT_Value { enum NT_Type type; unsigned long long last_change; union { - int v_boolean; + NT_Bool v_boolean; double v_double; struct NT_String v_string; struct NT_String v_raw; struct { - int* arr; + NT_Bool* arr; size_t size; } arr_boolean; struct { @@ -111,6 +130,9 @@ struct NT_Value { /** NetworkTables Entry Information */ struct NT_EntryInfo { + /** Entry handle */ + NT_Entry entry; + /** Entry name */ struct NT_String name; @@ -126,26 +148,44 @@ struct NT_EntryInfo { /** NetworkTables Connection Information */ struct NT_ConnectionInfo { + /** + * The remote identifier (as set on the remote node by + * NetworkTableInstance::SetNetworkIdentity() or nt::SetNetworkIdentity()). + */ struct NT_String remote_id; + + /** The IP address of the remote node. */ struct NT_String remote_ip; + + /** The port number of the remote node. */ unsigned int remote_port; + + /** + * The last time any update was received from the remote node (same scale as + * returned by nt::Now()). + */ unsigned long long last_update; + + /** + * The protocol version being used for this connection. This in protocol + * layer format, so 0x0200 = 2.0, 0x0300 = 3.0). + */ unsigned int protocol_version; }; -/** NetworkTables RPC Parameter Definition */ +/** NetworkTables RPC Version 1 Definition Parameter */ struct NT_RpcParamDef { struct NT_String name; struct NT_Value def_value; }; -/** NetworkTables RPC Result Definition */ +/** NetworkTables RPC Version 1 Definition Result */ struct NT_RpcResultDef { struct NT_String name; enum NT_Type type; }; -/** NetworkTables RPC Definition */ +/** NetworkTables RPC Version 1 Definition */ struct NT_RpcDefinition { unsigned int version; struct NT_String name; @@ -156,58 +196,193 @@ struct NT_RpcDefinition { }; /** NetworkTables RPC Call Data */ -struct NT_RpcCallInfo { - unsigned int rpc_id; - unsigned int call_uid; +struct NT_RpcAnswer { + NT_Entry entry; + NT_RpcCall call; struct NT_String name; struct NT_String params; + struct NT_ConnectionInfo conn; }; -/* - * Table Functions +/** NetworkTables Entry Notification */ +struct NT_EntryNotification { + /** Listener that was triggered. */ + NT_EntryListener listener; + + /** Entry handle. */ + NT_Entry entry; + + /** Entry name. */ + struct NT_String name; + + /** The new value. */ + struct NT_Value value; + + /** + * Update flags. For example, NT_NOTIFY_NEW if the key did not previously + * exist. + */ + unsigned int flags; +}; + +/** NetworkTables Connection Notification */ +struct NT_ConnectionNotification { + /** Listener that was triggered. */ + NT_ConnectionListener listener; + + /** True if event is due to connection being established. */ + NT_Bool connected; + + /** Connection info. */ + struct NT_ConnectionInfo conn; +}; + +/** NetworkTables log message. */ +struct NT_LogMessage { + /** The logger that generated the message. */ + NT_Logger logger; + + /** Log level of the message. See NT_LogLevel. */ + unsigned int level; + + /** The filename of the source file that generated the message. */ + const char* filename; + + /** The line number in the source file that generated the message. */ + unsigned int line; + + /** The message. */ + char* message; +}; + +/** + * @defgroup InstanceFunctions Instance Functions + * @{ + */ + +/** + * Get default instance. + * This is the instance used by non-handle-taking functions. + * @return Instance handle + */ +NT_Inst NT_GetDefaultInstance(void); + +/** + * Create an instance. + * @return Instance handle + */ +NT_Inst NT_CreateInstance(void); + +/** + * Destroy an instance. + * The default instance cannot be destroyed. + * @param inst Instance handle + */ +void NT_DestroyInstance(NT_Inst inst); + +/** + * Get instance handle from another handle. + * @param handle handle + * @return Instance handle + */ +NT_Inst NT_GetInstanceFromHandle(NT_Handle handle); + +/** @} */ + +/** + * @defgroup TableFunctions Table Functions + * @{ + */ + +/** + * Get Entry Handle. + * @param inst instance handle + * @param name entry name (UTF-8 string) + * @param name_len length of name in bytes + * @return entry handle + */ +NT_Entry NT_GetEntry(NT_Inst inst, const char* name, size_t name_len); + +/** + * Get Entry Handles. + * Returns an array of entry handles. The results are optionally + * filtered by string prefix and entry type to only return a subset of all + * entries. + * + * @param prefix entry name required prefix; only entries whose name + * starts with this string are returned + * @param prefix_len length of prefix in bytes + * @param types bitmask of NT_Type values; 0 is treated specially + * as a "don't care" + * @return Array of entry handles. */ +NT_Entry* NT_GetEntries(NT_Inst inst, const char* prefix, size_t prefix_len, + unsigned int types, size_t* count); -/** Get Entry Value. +/** + * Gets the name of the specified entry. + * Returns an empty string if the handle is invalid. + * @param entry entry handle + * @param name_len length of the returned string (output parameter) + * @return Entry name + */ +char* NT_GetEntryName(NT_Entry entry, size_t* name_len); + +/** + * Gets the type for the specified key, or unassigned if non existent. + * @param entry entry handle + * @return Entry type + */ +enum NT_Type NT_GetEntryType(NT_Entry entry); + +/** + * Gets the last time the entry was changed. + * Returns 0 if the handle is invalid. + * @param entry entry handle + * @return Entry last change time + */ +unsigned long long NT_GetEntryLastChange(NT_Entry entry); + +/** + * Get Entry Value. * Returns copy of current entry value. * Note that one of the type options is "unassigned". * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param value storage for returned entry value * * It is the caller's responsibility to free value once it's no longer * needed (the utility function NT_DisposeValue() is useful for this * purpose). */ -void NT_GetEntryValue(const char* name, size_t name_len, - struct NT_Value* value); +void NT_GetEntryValue(NT_Entry entry, struct NT_Value* value); -/** Set Default Entry Value. +/** + * Set Default Entry Value. * Returns copy of current entry value if it exists. * Otherwise, sets passed in value, and returns set value. * Note that one of the type options is "unassigned". * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param default_value value to be set if name does not exist * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryValue(const char* name, size_t name_len, - const struct NT_Value* default_value); +NT_Bool NT_SetDefaultEntryValue(NT_Entry entry, + const struct NT_Value* default_value); -/** Set Entry Value. +/** + * Set Entry Value. * Sets new entry value. If type of new value differs from the type of the * currently stored entry, returns error and does not update value. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param value new entry value * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryValue(const char* name, size_t name_len, - const struct NT_Value* value); +NT_Bool NT_SetEntryValue(NT_Entry entry, const struct NT_Value* value); -/** Set Entry Type and Value. +/** + * Set Entry Type and Value. * Sets new entry value. If type of new value differs from the type of the * currently stored entry, the currently stored entry type is overridden * (generally this will generate an Entry Assignment message). @@ -215,22 +390,27 @@ int NT_SetEntryValue(const char* name, size_t name_len, * This is NOT the preferred method to update a value; generally * NT_SetEntryValue() should be used instead, with appropriate error handling. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param value new entry value */ -void NT_SetEntryTypeValue(const char* name, size_t name_len, - const struct NT_Value* value); +void NT_SetEntryTypeValue(NT_Entry entry, const struct NT_Value* value); -/** Set Entry Flags. +/** + * Set Entry Flags. + * @param entry entry handle + * @param flags flags value (bitmask of NT_EntryFlags) */ -void NT_SetEntryFlags(const char* name, size_t name_len, unsigned int flags); +void NT_SetEntryFlags(NT_Entry entry, unsigned int flags); -/** Get Entry Flags. +/** + * Get Entry Flags. + * @param entry entry handle + * @return Flags value (bitmask of NT_EntryFlags) */ -unsigned int NT_GetEntryFlags(const char* name, size_t name_len); +unsigned int NT_GetEntryFlags(NT_Entry entry); -/** Delete Entry. +/** + * Delete Entry. * Deletes an entry. This is a new feature in version 3.0 of the protocol, * so this may not have an effect if any other node in the network is not * version 3.0 or newer. @@ -239,12 +419,12 @@ unsigned int NT_GetEntryFlags(const char* name, size_t name_len); * of direct remote connection(s), but this is not sufficient to determine * if all nodes in the network are version 3.0 or newer. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle */ -void NT_DeleteEntry(const char* name, size_t name_len); +void NT_DeleteEntry(NT_Entry entry); -/** Delete All Entries. +/** + * Delete All Entries. * Deletes ALL table entries. This is a new feature in version 3.0 of the * so this may not have an effect if any other node in the network is not * version 3.0 or newer. @@ -252,15 +432,19 @@ void NT_DeleteEntry(const char* name, size_t name_len); * Note: NT_GetConnections() can be used to determine the protocol version * of direct remote connection(s), but this is not sufficient to determine * if all nodes in the network are version 3.0 or newer. + * + * @param inst instance handle */ -void NT_DeleteAllEntries(void); +void NT_DeleteAllEntries(NT_Inst inst); -/** Get Entry Information. - * Returns an array of entry information (name, entry type, +/** + * Get Entry Information. + * Returns an array of entry information (entry handle, name, entry type, * and timestamp of last change to type/value). The results are optionally * filtered by string prefix and entry type to only return a subset of all * entries. * + * @param inst instance handle * @param prefix entry name required prefix; only entries whose name * starts with this string are returned * @param prefix_len length of prefix in bytes @@ -269,281 +453,940 @@ void NT_DeleteAllEntries(void); * @param count output parameter; set to length of returned array * @return Array of entry information. */ -struct NT_EntryInfo* NT_GetEntryInfo(const char* prefix, size_t prefix_len, - unsigned int types, size_t* count); +struct NT_EntryInfo* NT_GetEntryInfo(NT_Inst inst, const char* prefix, + size_t prefix_len, unsigned int types, + size_t* count); -/** Flush Entries. - * Forces an immediate flush of all local entry changes to network. - * Normally this is done on a regularly scheduled interval (see - * NT_SetUpdateRate()). +/** + * Get Entry Information. + * Returns information about an entry (name, entry type, + * and timestamp of last change to type/value). * - * Note: flushes are rate limited to avoid excessive network traffic. If - * the time between calls is too short, the flush will occur after the minimum - * time elapses (rather than immediately). + * @param entry entry handle + * @param info entry information (output) + * @return True if successful, false on error. */ -void NT_Flush(void); +NT_Bool NT_GetEntryInfoHandle(NT_Entry entry, struct NT_EntryInfo* info); -/* - * Callback Creation Functions +/** @} */ + +/** + * @defgroup EntryListenerFunctions Entry Listener Functions + * @{ + */ + +/** + * Entry listener callback function. + * Called when a key-value pair is changed. + * + * @param data data pointer provided to callback creation function + * @param event event information + */ +typedef void (*NT_EntryListenerCallback)(void* data, + const NT_EntryNotification* event); + +/** + * Add a listener for all entries starting with a certain prefix. + * + * @param inst instance handle + * @param prefix UTF-8 string prefix + * @param prefix_len length of prefix in bytes + * @param data data pointer to pass to callback + * @param callback listener to add + * @param flags NT_NotifyKind bitmask + * @return Listener handle + */ +NT_EntryListener NT_AddEntryListener(NT_Inst inst, const char* prefix, + size_t prefix_len, void* data, + NT_EntryListenerCallback callback, + unsigned int flags); + +/** + * Add a listener for a single entry. + * + * @param entry entry handle + * @param data data pointer to pass to callback + * @param callback listener to add + * @param flags NT_NotifyKind bitmask + * @return Listener handle + */ +NT_EntryListener NT_AddEntryListenerSingle(NT_Entry entry, void* data, + NT_EntryListenerCallback callback, + unsigned int flags); + +/** + * Create a entry listener poller. + * A poller provides a single queue of poll events. Events linked to this + * poller (using NT_AddPolledEntryListener()) will be stored in the queue and + * must be collected by calling NT_PollEntryListener(). + * The returned handle must be destroyed with NT_DestroyEntryListenerPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_EntryListenerPoller NT_CreateEntryListenerPoller(NT_Inst inst); + +/** + * Destroy a entry listener poller. This will abort any blocked polling + * call and prevent additional events from being generated for this poller. + * @param poller poller handle + */ +void NT_DestroyEntryListenerPoller(NT_EntryListenerPoller poller); + +/** + * Create a polled entry listener. + * The caller is responsible for calling NT_PollEntryListener() to poll. + * @param poller poller handle + * @param prefix UTF-8 string prefix + * @param flags NT_NotifyKind bitmask + * @return Listener handle + */ +NT_EntryListener NT_AddPolledEntryListener(NT_EntryListenerPoller poller, + const char* prefix, + size_t prefix_len, + unsigned int flags); + +/** + * Create a polled entry listener. + * The caller is responsible for calling NT_PollEntryListener() to poll. + * @param poller poller handle + * @param prefix UTF-8 string prefix + * @param flags NT_NotifyKind bitmask + * @return Listener handle + */ +NT_EntryListener NT_AddPolledEntryListenerSingle(NT_EntryListenerPoller poller, + NT_Entry entry, + unsigned int flags); + +/** + * Get the next entry listener event. This blocks until the next event occurs. + * This is intended to be used with NT_AddPolledEntryListener(); entry listeners + * created using NT_AddEntryListener() will not be serviced through this + * function. + * @param poller poller handle + * @param len length of returned array (output) + * @return Array of information on the entry listener events. Returns NULL if + * an erroroccurred (e.g. the instance was invalid or is shutting down). + */ +struct NT_EntryNotification* NT_PollEntryListener(NT_EntryListenerPoller poller, + size_t* len); + +/** + * Get the next entry listener event. This blocks until the next event occurs + * or it times out. This is intended to be used with + * NT_AddPolledEntryListener(); entry listeners created using + * NT_AddEntryListener() will not be serviced through this function. + * @param poller poller handle + * @param len length of returned array (output) + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Array of information on the entry listener events. If NULL is + * returned and timed_out is also false, an error occurred (e.g. the + * instance was invalid or is shutting down). + */ +struct NT_EntryNotification* NT_PollEntryListenerTimeout( + NT_EntryListenerPoller poller, size_t* len, double timeout, + NT_Bool* timed_out); + +/** + * Cancel a PollEntryListener call. This wakes up a call to + * PollEntryListener for this poller and causes it to immediately return + * an empty array. + * @param poller poller handle + */ +void NT_CancelPollEntryListener(NT_EntryListenerPoller poller); + +/** + * Remove an entry listener. + * @param entry_listener Listener handle to remove */ +void NT_RemoveEntryListener(NT_EntryListener entry_listener); -void NT_SetListenerOnStart(void (*on_start)(void* data), void* data); -void NT_SetListenerOnExit(void (*on_exit)(void* data), void* data); +/** + * Wait for the entry listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the entry listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +NT_Bool NT_WaitForEntryListenerQueue(NT_Inst inst, double timeout); -typedef void (*NT_EntryListenerCallback)(unsigned int uid, void* data, - const char* name, size_t name_len, - const struct NT_Value* value, - unsigned int flags); +/** @} */ + +/** + * @defgroup ConnectionListenerFunctions Connection Listener Functions + * @{ + */ +/** + * Connection listener callback function. + * Called when a network connection is made or lost. + * + * @param data data pointer provided to callback creation function + * @param event event info + */ typedef void (*NT_ConnectionListenerCallback)( - unsigned int uid, void* data, int connected, - const struct NT_ConnectionInfo* conn); + void* data, const struct NT_ConnectionNotification* event); -unsigned int NT_AddEntryListener(const char* prefix, size_t prefix_len, - void* data, NT_EntryListenerCallback callback, - unsigned int flags); -void NT_RemoveEntryListener(unsigned int entry_listener_uid); -unsigned int NT_AddConnectionListener(void* data, - NT_ConnectionListenerCallback callback, - int immediate_notify); -void NT_RemoveConnectionListener(unsigned int conn_listener_uid); +/** + * Add a connection listener. + * + * @param inst instance handle + * @param data data pointer to pass to callback + * @param callback listener to add + * @param immediate_notify notify listener of all existing connections + * @return Listener handle + */ +NT_ConnectionListener NT_AddConnectionListener( + NT_Inst inst, void* data, NT_ConnectionListenerCallback callback, + NT_Bool immediate_notify); -int NT_NotifierDestroyed(); +/** + * Create a connection listener poller. + * A poller provides a single queue of poll events. Events linked to this + * poller (using NT_AddPolledConnectionListener()) will be stored in the queue + * and must be collected by calling NT_PollConnectionListener(). + * The returned handle must be destroyed with + * NT_DestroyConnectionListenerPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_ConnectionListenerPoller NT_CreateConnectionListenerPoller(NT_Inst inst); -/* - * Remote Procedure Call Functions +/** + * Destroy a connection listener poller. This will abort any blocked polling + * call and prevent additional events from being generated for this poller. + * @param poller poller handle + */ +void NT_DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller); + +/** + * Create a polled connection listener. + * The caller is responsible for calling NT_PollConnectionListener() to poll. + * @param poller poller handle + * @param immediate_notify notify listener of all existing connections + */ +NT_ConnectionListener NT_AddPolledConnectionListener( + NT_ConnectionListenerPoller poller, NT_Bool immediate_notify); + +/** + * Get the next connection event. This blocks until the next connect or + * disconnect occurs. This is intended to be used with + * NT_AddPolledConnectionListener(); connection listeners created using + * NT_AddConnectionListener() will not be serviced through this function. + * @param poller poller handle + * @param len length of returned array (output) + * @return Array of information on the connection events. Only returns NULL + * if an error occurred (e.g. the instance was invalid or is shutting + * down). + */ +struct NT_ConnectionNotification* NT_PollConnectionListener( + NT_ConnectionListenerPoller poller, size_t* len); + +/** + * Get the next connection event. This blocks until the next connect or + * disconnect occurs or it times out. This is intended to be used with + * NT_AddPolledConnectionListener(); connection listeners created using + * NT_AddConnectionListener() will not be serviced through this function. + * @param poller poller handle + * @param len length of returned array (output) + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Array of information on the connection events. If NULL is returned + * and timed_out is also false, an error occurred (e.g. the instance + * was invalid or is shutting down). + */ +struct NT_ConnectionNotification* NT_PollConnectionListenerTimeout( + NT_ConnectionListenerPoller poller, size_t* len, double timeout, + NT_Bool* timed_out); + +/** + * Cancel a PollConnectionListener call. This wakes up a call to + * PollConnectionListener for this poller and causes it to immediately return + * an empty array. + * @param poller poller handle */ +void NT_CancelPollConnectionListener(NT_ConnectionListenerPoller poller); -void NT_SetRpcServerOnStart(void (*on_start)(void* data), void* data); -void NT_SetRpcServerOnExit(void (*on_exit)(void* data), void* data); +/** + * Remove a connection listener. + * @param conn_listener Listener handle to remove + */ +void NT_RemoveConnectionListener(NT_ConnectionListener conn_listener); + +/** + * Wait for the connection listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the connection listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +NT_Bool NT_WaitForConnectionListenerQueue(NT_Inst inst, double timeout); + +/** @} */ + +/** + * @defgroup RpcFunctions Remote Procedure Call Functions + * @{ + */ + +/** + * Remote Procedure Call (RPC) callback function. + * @param data data pointer provided to NT_CreateRpc() + * @param call call information + * Note: NT_PostRpcResponse() must be called by the callback to provide a + * response to the call. + */ +typedef void (*NT_RpcCallback)(void* data, const struct NT_RpcAnswer* call); + +/** + * Create a callback-based RPC entry point. Only valid to use on the server. + * The callback function will be called when the RPC is called. + * @param entry entry handle of RPC entry + * @param def RPC definition + * @param def_len length of def in bytes + * @param data data pointer to pass to callback function + * @param callback callback function + */ +void NT_CreateRpc(NT_Entry entry, const char* def, size_t def_len, void* data, + NT_RpcCallback callback); + +/** + * Create a RPC call poller. Only valid to use on the server. + * A poller provides a single queue of poll events. Events linked to this + * poller (using NT_CreatePolledRpc()) will be stored in the queue and must be + * collected by calling NT_PollRpc() or NT_PollRpcTimeout(). + * The returned handle must be destroyed with NT_DestroyRpcCallPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_RpcCallPoller NT_CreateRpcCallPoller(NT_Inst inst); + +/** + * Destroy a RPC call poller. This will abort any blocked polling call and + * prevent additional events from being generated for this poller. + * @param poller poller handle + */ +void NT_DestroyRpcCallPoller(NT_RpcCallPoller poller); + +/** + * Create a polled RPC entry point. Only valid to use on the server. + * The caller is responsible for calling NT_PollRpc() or NT_PollRpcTimeout() + * to poll for servicing incoming RPC calls. + * @param entry entry handle of RPC entry + * @param def RPC definition + * @param def_len length of def in bytes + * @param poller poller handle + */ +void NT_CreatePolledRpc(NT_Entry entry, const char* def, size_t def_len, + NT_RpcCallPoller poller); + +/** + * Get the next incoming RPC call. This blocks until the next incoming RPC + * call is received. This is intended to be used with NT_CreatePolledRpc(); + * RPC calls created using NT_CreateRpc() will not be serviced through this + * function. Upon successful return, NT_PostRpcResponse() must be called to + * send the return value to the caller. The returned array must be freed + * using NT_DisposeRpcAnswerArray(). + * @param poller poller handle + * @param len length of returned array (output) + * @return Array of RPC call information. Only returns NULL if an error + * occurred (e.g. the instance was invalid or is shutting down). + */ +struct NT_RpcAnswer* NT_PollRpc(NT_RpcCallPoller poller, size_t* len); + +/** + * Get the next incoming RPC call. This blocks until the next incoming RPC + * call is received or it times out. This is intended to be used with + * NT_CreatePolledRpc(); RPC calls created using NT_CreateRpc() will not be + * serviced through this function. Upon successful return, + * NT_PostRpcResponse() must be called to send the return value to the caller. + * The returned array must be freed using NT_DisposeRpcAnswerArray(). + * @param poller poller handle + * @param len length of returned array (output) + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Array of RPC call information. If NULL is returned and timed_out + * is also false, an error occurred (e.g. the instance was invalid or + * is shutting down). + */ +struct NT_RpcAnswer* NT_PollRpcTimeout(NT_RpcCallPoller poller, size_t* len, + double timeout, NT_Bool* timed_out); + +/** + * Cancel a PollRpc call. This wakes up a call to PollRpc for this poller + * and causes it to immediately return an empty array. + * @param poller poller handle + */ +void NT_CancelPollRpc(NT_RpcCallPoller poller); + +/** + * Wait for the incoming RPC call queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the RPC call + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +NT_Bool NT_WaitForRpcCallQueue(NT_Inst inst, double timeout); -typedef char* (*NT_RpcCallback)(void* data, const char* name, size_t name_len, - const char* params, size_t params_len, - size_t* results_len, - const struct NT_ConnectionInfo* conn_info); +/** + * Post RPC response (return value) for a polled RPC. + * The rpc and call parameters should come from the NT_RpcAnswer returned + * by NT_PollRpc(). + * @param entry entry handle of RPC entry (from NT_RpcAnswer) + * @param call RPC call handle (from NT_RpcAnswer) + * @param result result raw data that will be provided to remote caller + * @param result_len length of result in bytes + */ +void NT_PostRpcResponse(NT_Entry entry, NT_RpcCall call, const char* result, + size_t result_len); -void NT_CreateRpc(const char* name, size_t name_len, const char* def, - size_t def_len, void* data, NT_RpcCallback callback); -void NT_CreatePolledRpc(const char* name, size_t name_len, const char* def, - size_t def_len); +/** + * Call a RPC function. May be used on either the client or server. + * This function is non-blocking. Either NT_GetRpcResult() or + * NT_CancelRpcResult() must be called to either get or ignore the result of + * the call. + * @param entry entry handle of RPC entry + * @param params parameter + * @param params_len length of param in bytes + * @return RPC call handle (for use with NT_GetRpcResult() or + * NT_CancelRpcResult()). + */ +NT_RpcCall NT_CallRpc(NT_Entry entry, const char* params, size_t params_len); -int NT_PollRpc(int blocking, struct NT_RpcCallInfo* call_info); -int NT_PollRpcTimeout(int blocking, double time_out, - struct NT_RpcCallInfo* call_info); -void NT_PostRpcResponse(unsigned int rpc_id, unsigned int call_uid, - const char* result, size_t result_len); +/** + * Get the result (return value) of a RPC call. This function blocks until + * the result is received. + * @param entry entry handle of RPC entry + * @param call RPC call handle returned by NT_CallRpc() + * @param result_len length of returned result in bytes + * @return NULL on error, or result. + */ +char* NT_GetRpcResult(NT_Entry entry, NT_RpcCall call, size_t* result_len); -unsigned int NT_CallRpc(const char* name, size_t name_len, const char* params, - size_t params_len); -char* NT_GetRpcResult(int blocking, unsigned int call_uid, size_t* result_len); -char* NT_GetRpcResultTimeout(int blocking, unsigned int call_uid, - double time_out, size_t* result_len); -void NT_CancelBlockingRpcResult(unsigned int call_uid); +/** + * Get the result (return value) of a RPC call. This function blocks until + * the result is received or it times out. + * @param entry entry handle of RPC entry + * @param call RPC call handle returned by NT_CallRpc() + * @param result_len length of returned result in bytes + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return NULL on error or timeout, or result. + */ +char* NT_GetRpcResultTimeout(NT_Entry entry, NT_RpcCall call, + size_t* result_len, double timeout, + NT_Bool* timed_out); +/** + * Ignore the result of a RPC call. This function is non-blocking. + * @param entry entry handle of RPC entry + * @param call RPC call handle returned by NT_CallRpc() + */ +void NT_CancelRpcResult(NT_Entry entry, NT_RpcCall call); + +/** + * Pack a RPC version 1 definition. + * @param def RPC version 1 definition + * @param packed_len length of return value in bytes + * @return Raw packed bytes. Use C standard library free() to release. + */ char* NT_PackRpcDefinition(const struct NT_RpcDefinition* def, size_t* packed_len); -int NT_UnpackRpcDefinition(const char* packed, size_t packed_len, - struct NT_RpcDefinition* def); + +/** + * Unpack a RPC version 1 definition. This can be used for introspection or + * validation. + * @param packed raw packed bytes + * @param packed_len length of packed in bytes + * @param def RPC version 1 definition (output) + * @return True if successfully unpacked, false otherwise. + */ +NT_Bool NT_UnpackRpcDefinition(const char* packed, size_t packed_len, + struct NT_RpcDefinition* def); + +/** + * Pack RPC values as required for RPC version 1 definition messages. + * @param values array of values to pack + * @param values_len length of values + * @param packed_len length of return value in bytes + * @return Raw packed bytes. Use C standard library free() to release. + */ char* NT_PackRpcValues(const struct NT_Value** values, size_t values_len, size_t* packed_len); + +/** + * Unpack RPC values as required for RPC version 1 definition messages. + * @param packed raw packed bytes + * @param packed_len length of packed in bytes + * @param types array of data types (as provided in the RPC definition) + * @param types_len length of types + * @return Array of NT_Value's. + */ struct NT_Value** NT_UnpackRpcValues(const char* packed, size_t packed_len, const NT_Type* types, size_t types_len); -/* - * Client/Server Functions +/** @} */ + +/** + * @defgroup NetworkFunctions Client/Server Functions + * @{ + */ + +/** + * Set the network identity of this node. + * This is the name used during the initial connection handshake, and is + * visible through NT_ConnectionInfo on the remote node. + * @param inst instance handle + * @param name identity to advertise + * @param name_len length of name in bytes */ -void NT_SetNetworkIdentity(const char* name, size_t name_len); +void NT_SetNetworkIdentity(NT_Inst inst, const char* name, size_t name_len); /** * Get the current network mode. + * @param inst instance handle + * @return Bitmask of NT_NetworkMode. */ -unsigned int NT_GetNetworkMode(); +unsigned int NT_GetNetworkMode(NT_Inst inst); -/** Start Server +/** * Starts a server using the specified filename, listening address, and port. * + * @param inst instance handle * @param persist_filename the name of the persist file to use (UTF-8 string, * null terminated) * @param listen_address the address to listen on, or null to listen on any * address. (UTF-8 string, null terminated) * @param port port to communicate over. */ -void NT_StartServer(const char* persist_filename, const char* listen_address, - unsigned int port); +void NT_StartServer(NT_Inst inst, const char* persist_filename, + const char* listen_address, unsigned int port); -/** Stop Server +/** * Stops the server if it is running. + * @param inst instance handle */ -void NT_StopServer(void); +void NT_StopServer(NT_Inst inst); -/** Starts Client +/** * Starts a client. Use NT_SetServer to set the server name and port. + * @param inst instance handle */ -void NT_StartClientNone(void); +void NT_StartClientNone(NT_Inst inst); -/** Starts Client +/** * Starts a client using the specified server and port * + * @param inst instance handle * @param server_name server name (UTF-8 string, null terminated) * @param port port to communicate over - * */ -void NT_StartClient(const char* server_name, unsigned int port); +void NT_StartClient(NT_Inst inst, const char* server_name, unsigned int port); -/** Starts Client +/** * Starts a client using the specified (server, port) combinations. The * client will attempt to connect to each server in round robin fashion. * + * @param inst instance handle * @param count length of the server_names and ports arrays * @param server_names array of server names (each a UTF-8 string, null * terminated) * @param ports array of ports to communicate over (one for each server) - * */ -void NT_StartClientMulti(size_t count, const char** server_names, +void NT_StartClientMulti(NT_Inst inst, size_t count, const char** server_names, const unsigned int* ports); -/** Stop Client +/** + * Starts a client using commonly known robot addresses for the specified team. + * + * @param inst instance handle + * @param team team number + * @param port port to communicate over + */ +void NT_StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port); + +/** * Stops the client if it is running. + * @param inst instance handle */ -void NT_StopClient(void); +void NT_StopClient(NT_Inst inst); -/** Sets server address for client (without restarting client). +/** + * Sets server address and port for client (without restarting client). * + * @param inst instance handle * @param server_name server name (UTF-8 string, null terminated) * @param port port to communicate over - * */ -void NT_SetServer(const char* server_name, unsigned int port); +void NT_SetServer(NT_Inst inst, const char* server_name, unsigned int port); -/** Sets server addresses for client (without restarting client). +/** + * Sets server addresses for client (without restarting client). * The client will attempt to connect to each server in round robin fashion. * + * @param inst instance handle * @param count length of the server_names and ports arrays * @param server_names array of server names (each a UTF-8 string, null * terminated) * @param ports array of ports to communicate over (one for each server) - * */ -void NT_SetServerMulti(size_t count, const char** server_names, +void NT_SetServerMulti(NT_Inst inst, size_t count, const char** server_names, const unsigned int* ports); -/** Starts requesting server address from Driver Station. +/** + * Sets server addresses and port for client (without restarting client). + * Connects using commonly known robot addresses for the specified team. + * + * @param inst instance handle + * @param team team number + * @param port port to communicate over + */ +void NT_SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port); + +/** + * Starts requesting server address from Driver Station. * This connects to the Driver Station running on localhost to obtain the * server IP address. * + * @param inst instance handle * @param port server port to use in combination with IP from DS */ -void NT_StartDSClient(unsigned int port); - -/** Stops requesting server address from Driver Station. */ -void NT_StopDSClient(void); +void NT_StartDSClient(NT_Inst inst, unsigned int port); -/** Stop Rpc Server - * Stops the Rpc server if it is running. +/** + * Stops requesting server address from Driver Station. + * @param inst instance handle */ -void NT_StopRpcServer(void); +void NT_StopDSClient(NT_Inst inst); -/** Stop Notifier - * Stops the Notifier (Entry and Connection Listener) thread if it is running. +/** + * Set the periodic update rate. + * Sets how frequently updates are sent to other nodes over the network. + * + * @param inst instance handle + * @param interval update interval in seconds (range 0.01 to 1.0) */ -void NT_StopNotifier(void); +void NT_SetUpdateRate(NT_Inst inst, double interval); -/** Set Update Rate - * Sets the update rate of the table +/** + * Flush Entries. + * Forces an immediate flush of all local entry changes to network. + * Normally this is done on a regularly scheduled interval (see + * NT_SetUpdateRate()). * - * @param interval the interval to update the table at (in seconds) + * Note: flushes are rate limited to avoid excessive network traffic. If + * the time between calls is too short, the flush will occur after the minimum + * time elapses (rather than immediately). * + * @param inst instance handle */ -void NT_SetUpdateRate(double interval); +void NT_Flush(NT_Inst inst); -/** Get Connections - * Gets an array of all the connections in the table. +/** + * Get information on the currently established network connections. + * If operating as a client, this will return either zero or one values. * + * @param inst instance handle * @param count returns the number of elements in the array - * @return an array containing all the connections. + * @return array of connection information * * It is the caller's responsibility to free the array. The * NT_DisposeConnectionInfoArray function is useful for this purpose. */ -struct NT_ConnectionInfo* NT_GetConnections(size_t* count); +struct NT_ConnectionInfo* NT_GetConnections(NT_Inst inst, size_t* count); -/* - * Persistent Functions +/** + * Return whether or not the instance is connected to another node. + * @param inst instance handle + * @return True if connected. + */ +NT_Bool NT_IsConnected(NT_Inst inst); + +/** @} */ + +/** + * @defgroup PersistentFunctions Persistent Functions + * @{ */ -/* return error string, or NULL if successful */ -const char* NT_SavePersistent(const char* filename); -const char* NT_LoadPersistent(const char* filename, + +/** + * Save persistent values to a file. The server automatically does this, + * but this function provides a way to save persistent values in the same + * format to a file on either a client or a server. + * @param inst instance handle + * @param filename filename + * @return error string, or NULL if successful + */ +const char* NT_SavePersistent(NT_Inst inst, const char* filename); + +/** + * Load persistent values from a file. The server automatically does this + * at startup, but this function provides a way to restore persistent values + * in the same format from a file at any time on either a client or a server. + * @param inst instance handle + * @param filename filename + * @param warn callback function for warnings + * @return error string, or NULL if successful + */ +const char* NT_LoadPersistent(NT_Inst inst, const char* filename, void (*warn)(size_t line, const char* msg)); -/* - * Utility Functions +/** @} */ + +/** + * @defgroup UtilityFunctions Utility Functions + * @{ */ -/* frees value memory */ +/** + * Frees value memory. + * @param value value to free + */ void NT_DisposeValue(struct NT_Value* value); -/* sets type to unassigned and clears rest of struct */ +/** + * Initializes a NT_Value. + * Sets type to NT_UNASSIGNED and clears rest of struct. + * @param value value to initialize + */ void NT_InitValue(struct NT_Value* value); -/* frees string memory */ +/** + * Frees string memory. + * @param str string to free + */ void NT_DisposeString(struct NT_String* str); -/* sets length to zero and pointer to null */ +/** + * Initializes a NT_String. + * Sets length to zero and pointer to null. + * @param str string to initialize + */ void NT_InitString(struct NT_String* str); -/* Gets the type for the specified key, or unassigned if non existent. */ -enum NT_Type NT_GetType(const char* name, size_t name_len); +/** + * Disposes an entry handle array. + * @param arr pointer to the array to dispose + * @param count number of elements in the array + */ +void NT_DisposeEntryArray(NT_Entry* arr, size_t count); -/** Dispose Connection Info Array - * Disposes a connection info array - * +/** + * Disposes a connection info array. * @param arr pointer to the array to dispose * @param count number of elements in the array - * */ void NT_DisposeConnectionInfoArray(struct NT_ConnectionInfo* arr, size_t count); -/** Dispose Entry Info Array - * Disposes an entry info array - * +/** + * Disposes an entry info array. * @param arr pointer to the array to dispose * @param count number of elements in the array - * */ void NT_DisposeEntryInfoArray(struct NT_EntryInfo* arr, size_t count); -/** Dispose Rpc Definition - * Disposes a Rpc Definition structure - * +/** + * Disposes a single entry info (as returned by NT_GetEntryInfoHandle). + * @param info pointer to the info to dispose + */ +void NT_DisposeEntryInfo(struct NT_EntryInfo* info); + +/** + * Disposes a Rpc Definition structure. * @param def pointer to the struct to dispose - * */ void NT_DisposeRpcDefinition(struct NT_RpcDefinition* def); -/** Dispose Rpc Call Info - * Disposes a Rpc Call Info structure - * - * @param def pointer to the struct to dispose - * +/** + * Disposes a Rpc Answer array. + * @param arr pointer to the array to dispose + * @param count number of elements in the array + */ +void NT_DisposeRpcAnswerArray(struct NT_RpcAnswer* arr, size_t count); + +/** + * Disposes a Rpc Answer structure. + * @param answer pointer to the struct to dispose + */ +void NT_DisposeRpcAnswer(struct NT_RpcAnswer* answer); + +/** + * Disposes an entry notification array. + * @param arr pointer to the array to dispose + * @param count number of elements in the array + */ +void NT_DisposeEntryNotificationArray(struct NT_EntryNotification* arr, + size_t count); + +/** + * Disposes a single entry notification. + * @param info pointer to the info to dispose + */ +void NT_DisposeEntryNotification(struct NT_EntryNotification* info); + +/** + * Disposes a connection notification array. + * @param arr pointer to the array to dispose + * @param count number of elements in the array + */ +void NT_DisposeConnectionNotificationArray( + struct NT_ConnectionNotification* arr, size_t count); + +/** + * Disposes a single connection notification. + * @param info pointer to the info to dispose + */ +void NT_DisposeConnectionNotification(struct NT_ConnectionNotification* info); + +/** + * Disposes a log message array. + * @param arr pointer to the array to dispose + * @param count number of elements in the array */ -void NT_DisposeRpcCallInfo(struct NT_RpcCallInfo* call_info); +void NT_DisposeLogMessageArray(struct NT_LogMessage* arr, size_t count); -/* timestamp */ +/** + * Disposes a single log message. + * @param info pointer to the info to dispose + */ +void NT_DisposeLogMessage(struct NT_LogMessage* info); + +/** + * Returns monotonic current time in 100 ns increments. + * This is the same time base used for entry and connection timestamps. + * This function is a compatibility wrapper around WPI_Now(). + * @return Timestamp + */ unsigned long long NT_Now(void); -/* logging */ -typedef void (*NT_LogFunc)(unsigned int level, const char* file, - unsigned int line, const char* msg); -void NT_SetLogger(NT_LogFunc func, unsigned int min_level); +/** @} */ -/* - * Interop Utility Functions +/** + * @defgroup LoggerFunctions Logger Functions + * @{ + */ + +/** + * Log function. + * @param data data pointer passed to NT_AddLogger() + * @param msg message information + */ +typedef void (*NT_LogFunc)(void* data, const struct NT_LogMessage* msg); + +/** + * Add logger callback function. By default, log messages are sent to stderr; + * this function sends log messages to the provided callback function instead. + * The callback function will only be called for log messages with level + * greater than or equal to min_level and less than or equal to max_level; + * messages outside this range will be silently ignored. + * + * @param inst instance handle + * @param data data pointer to pass to func + * @param func log callback function + * @param min_level minimum log level + * @param max_level maximum log level + * @return Logger handle + */ +NT_Logger NT_AddLogger(NT_Inst inst, void* data, NT_LogFunc func, + unsigned int min_level, unsigned int max_level); + +/** + * Create a log poller. A poller provides a single queue of poll events. + * The returned handle must be destroyed with NT_DestroyLoggerPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_LoggerPoller NT_CreateLoggerPoller(NT_Inst inst); + +/** + * Destroy a log poller. This will abort any blocked polling call and prevent + * additional events from being generated for this poller. + * @param poller poller handle + */ +void NT_DestroyLoggerPoller(NT_LoggerPoller poller); + +/** + * Set the log level for a log poller. Events will only be generated for + * log messages with level greater than or equal to min_level and less than or + * equal to max_level; messages outside this range will be silently ignored. + * @param poller poller handle + * @param min_level minimum log level + * @param max_level maximum log level + * @return Logger handle + */ +NT_Logger NT_AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, + unsigned int max_level); + +/** + * Get the next log event. This blocks until the next log occurs. + * @param poller poller handle + * @param len length of returned array (output) + * @return Array of information on the log events. Only returns NULL if an + * error occurred (e.g. the instance was invalid or is shutting down). + */ +struct NT_LogMessage* NT_PollLogger(NT_LoggerPoller poller, size_t* len); + +/** + * Get the next log event. This blocks until the next log occurs or it times + * out. + * @param poller poller handle + * @param len length of returned array (output) + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Array of information on the log events. If NULL is returned and + * timed_out is also false, an error occurred (e.g. the instance was + * invalid or is shutting down). + */ +struct NT_LogMessage* NT_PollLoggerTimeout(NT_LoggerPoller poller, size_t* len, + double timeout, NT_Bool* timed_out); + +/** + * Cancel a PollLogger call. This wakes up a call to PollLogger for this + * poller and causes it to immediately return an empty array. + * @param poller poller handle + */ +void NT_CancelPollLogger(NT_LoggerPoller poller); + +/** + * Remove a logger. + * @param logger Logger handle to remove */ +void NT_RemoveLogger(NT_Logger logger); -/* Memory Allocators */ +/** + * Wait for the incoming log event queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the log event + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +NT_Bool NT_WaitForLoggerQueue(NT_Inst inst, double timeout); -/** Allocate Character Array +/** @} */ + +/** + * @defgroup InteropFunctions Interop Utility Functions + * @{ + */ + +/** + * @defgroup MemoryAllocators Memory Allocators + * @{ + */ + +/** * Allocates an array of chars. * Note that the size is the number of elements, and not the * specific number of bytes to allocate. That is calculated internally. @@ -556,7 +1399,7 @@ void NT_SetLogger(NT_LogFunc func, unsigned int min_level); */ char* NT_AllocateCharArray(size_t size); -/** Allocate Boolean Array +/** * Allocates an array of booleans. * Note that the size is the number of elements, and not the * specific number of bytes to allocate. That is calculated internally. @@ -567,9 +1410,9 @@ char* NT_AllocateCharArray(size_t size); * After use, the array should be freed using the NT_FreeBooleanArray() * function. */ -int* NT_AllocateBooleanArray(size_t size); +NT_Bool* NT_AllocateBooleanArray(size_t size); -/** Allocate Double Array +/** * Allocates an array of doubles. * Note that the size is the number of elements, and not the * specific number of bytes to allocate. That is calculated internally. @@ -582,7 +1425,7 @@ int* NT_AllocateBooleanArray(size_t size); */ double* NT_AllocateDoubleArray(size_t size); -/** Allocate NT_String Array +/** * Allocates an array of NT_Strings. * Note that the size is the number of elements, and not the * specific number of bytes to allocate. That is calculated internally. @@ -595,28 +1438,28 @@ double* NT_AllocateDoubleArray(size_t size); */ struct NT_String* NT_AllocateStringArray(size_t size); -/** Free Char Array +/** * Frees an array of chars. * * @param v_boolean pointer to the char array to free */ void NT_FreeCharArray(char* v_char); -/** Free Double Array +/** * Frees an array of doubles. * * @param v_boolean pointer to the double array to free */ void NT_FreeDoubleArray(double* v_double); -/** Free Boolean Array +/** * Frees an array of booleans. * * @param v_boolean pointer to the boolean array to free */ -void NT_FreeBooleanArray(int* v_boolean); +void NT_FreeBooleanArray(NT_Bool* v_boolean); -/** Free String Array +/** * Frees an array of NT_Strings. * * @param v_string pointer to the string array to free @@ -628,7 +1471,14 @@ void NT_FreeBooleanArray(int* v_boolean); */ void NT_FreeStringArray(struct NT_String* v_string, size_t arr_size); -/** Get Value Type +/** @} */ + +/** + * @defgroup TypedGetters Typed Getters + * @{ + */ + +/** * Returns the type of an NT_Value struct. * Note that one of the type options is "unassigned". * @@ -637,7 +1487,7 @@ void NT_FreeStringArray(struct NT_String* v_string, size_t arr_size); */ enum NT_Type NT_GetValueType(const struct NT_Value* value); -/** Get Value Boolean +/** * Returns the boolean from the NT_Value. * If the NT_Value is null, or is assigned to a different type, returns 0. * @@ -646,10 +1496,10 @@ enum NT_Type NT_GetValueType(const struct NT_Value* value); * @param v_boolean returns the boolean assigned to the name * @return 1 if successful, or 0 if value is null or not a boolean */ -int NT_GetValueBoolean(const struct NT_Value* value, - unsigned long long* last_change, int* v_boolean); +NT_Bool NT_GetValueBoolean(const struct NT_Value* value, + unsigned long long* last_change, NT_Bool* v_boolean); -/** Get Value Double +/** * Returns the double from the NT_Value. * If the NT_Value is null, or is assigned to a different type, returns 0. * @@ -658,10 +1508,10 @@ int NT_GetValueBoolean(const struct NT_Value* value, * @param v_double returns the boolean assigned to the name * @return 1 if successful, or 0 if value is null or not a double */ -int NT_GetValueDouble(const struct NT_Value* value, - unsigned long long* last_change, double* v_double); +NT_Bool NT_GetValueDouble(const struct NT_Value* value, + unsigned long long* last_change, double* v_double); -/** Get Value String +/** * Returns a copy of the string from the NT_Value. * If the NT_Value is null, or is assigned to a different type, returns 0. * @@ -678,7 +1528,7 @@ int NT_GetValueDouble(const struct NT_Value* value, char* NT_GetValueString(const struct NT_Value* value, unsigned long long* last_change, size_t* str_len); -/** Get Value Raw +/** * Returns a copy of the raw value from the NT_Value. * If the NT_Value is null, or is assigned to a different type, returns null. * @@ -695,7 +1545,7 @@ char* NT_GetValueString(const struct NT_Value* value, char* NT_GetValueRaw(const struct NT_Value* value, unsigned long long* last_change, size_t* raw_len); -/** Get Value Boolean Array +/** * Returns a copy of the boolean array from the NT_Value. * If the NT_Value is null, or is assigned to a different type, returns null. * @@ -709,10 +1559,11 @@ char* NT_GetValueRaw(const struct NT_Value* value, * The returned array is a copy of the array in the value, and must be * freed separately. */ -int* NT_GetValueBooleanArray(const struct NT_Value* value, - unsigned long long* last_change, size_t* arr_size); +NT_Bool* NT_GetValueBooleanArray(const struct NT_Value* value, + unsigned long long* last_change, + size_t* arr_size); -/** Get Value Double Array +/** * Returns a copy of the double array from the NT_Value. * If the NT_Value is null, or is assigned to a different type, returns null. * @@ -730,7 +1581,7 @@ double* NT_GetValueDoubleArray(const struct NT_Value* value, unsigned long long* last_change, size_t* arr_size); -/** Get Value String Array +/** * Returns a copy of the NT_String array from the NT_Value. * If the NT_Value is null, or is assigned to a different type, returns null. * @@ -750,43 +1601,40 @@ NT_String* NT_GetValueStringArray(const struct NT_Value* value, unsigned long long* last_change, size_t* arr_size); -/** Get Entry Boolean +/** * Returns the boolean currently assigned to the entry name. * If the entry name is not currently assigned, or is assigned to a * different type, returns 0. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param last_change returns time in ms since the last change in the value * @param v_boolean returns the boolean assigned to the name * @return 1 if successful, or 0 if value is unassigned or not a * boolean */ -int NT_GetEntryBoolean(const char* name, size_t name_len, - unsigned long long* last_change, int* v_boolean); +NT_Bool NT_GetEntryBoolean(NT_Entry entry, unsigned long long* last_change, + NT_Bool* v_boolean); -/** Get Entry Double +/** * Returns the double currently assigned to the entry name. * If the entry name is not currently assigned, or is assigned to a * different type, returns 0. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param last_change returns time in ms since the last change in the value * @param v_double returns the double assigned to the name * @return 1 if successful, or 0 if value is unassigned or not a * double */ -int NT_GetEntryDouble(const char* name, size_t name_len, - unsigned long long* last_change, double* v_double); +NT_Bool NT_GetEntryDouble(NT_Entry entry, unsigned long long* last_change, + double* v_double); -/** Get Entry String +/** * Returns a copy of the string assigned to the entry name. * If the entry name is not currently assigned, or is assigned to a * different type, returns null. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param last_change returns time in ms since the last change in the value * @param str_len returns the length of the string * @return pointer to the string (UTF-8), or null if error @@ -794,16 +1642,15 @@ int NT_GetEntryDouble(const char* name, size_t name_len, * It is the caller's responsibility to free the string once its no longer * needed. The NT_FreeCharArray() function is useful for this purpose. */ -char* NT_GetEntryString(const char* name, size_t name_len, - unsigned long long* last_change, size_t* str_len); +char* NT_GetEntryString(NT_Entry entry, unsigned long long* last_change, + size_t* str_len); -/** Get Entry Raw +/** * Returns a copy of the raw value assigned to the entry name. * If the entry name is not currently assigned, or is assigned to a * different type, returns null. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param last_change returns time in ms since the last change in the value * @param raw_len returns the length of the string * @return pointer to the raw value (UTF-8), or null if error @@ -811,16 +1658,15 @@ char* NT_GetEntryString(const char* name, size_t name_len, * It is the caller's responsibility to free the raw value once its no longer * needed. The NT_FreeCharArray() function is useful for this purpose. */ -char* NT_GetEntryRaw(const char* name, size_t name_len, - unsigned long long* last_change, size_t* raw_len); +char* NT_GetEntryRaw(NT_Entry entry, unsigned long long* last_change, + size_t* raw_len); -/** Get Entry Boolean Array +/** * Returns a copy of the boolean array assigned to the entry name. * If the entry name is not currently assigned, or is assigned to a * different type, returns null. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param last_change returns time in ms since the last change in the value * @param arr_size returns the number of elements in the array * @return pointer to the boolean array, or null if error @@ -828,16 +1674,16 @@ char* NT_GetEntryRaw(const char* name, size_t name_len, * It is the caller's responsibility to free the array once its no longer * needed. The NT_FreeBooleanArray() function is useful for this purpose. */ -int* NT_GetEntryBooleanArray(const char* name, size_t name_len, - unsigned long long* last_change, size_t* arr_size); +NT_Bool* NT_GetEntryBooleanArray(NT_Entry entry, + unsigned long long* last_change, + size_t* arr_size); -/** Get Entry Double Array +/** * Returns a copy of the double array assigned to the entry name. * If the entry name is not currently assigned, or is assigned to a * different type, returns null. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param last_change returns time in ms since the last change in the value * @param arr_size returns the number of elements in the array * @return pointer to the double array, or null if error @@ -845,17 +1691,15 @@ int* NT_GetEntryBooleanArray(const char* name, size_t name_len, * It is the caller's responsibility to free the array once its no longer * needed. The NT_FreeDoubleArray() function is useful for this purpose. */ -double* NT_GetEntryDoubleArray(const char* name, size_t name_len, - unsigned long long* last_change, +double* NT_GetEntryDoubleArray(NT_Entry entry, unsigned long long* last_change, size_t* arr_size); -/** Get Entry String Array +/** * Returns a copy of the NT_String array assigned to the entry name. * If the entry name is not currently assigned, or is assigned to a * different type, returns null. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle * @param last_change returns time in ms since the last change in the value * @param arr_size returns the number of elements in the array * @return pointer to the NT_String array, or null if error @@ -866,208 +1710,222 @@ double* NT_GetEntryDoubleArray(const char* name, size_t name_len, * should be freed at once. The NT_FreeStringArray() function will free all the * NT_Strings. */ -NT_String* NT_GetEntryStringArray(const char* name, size_t name_len, +NT_String* NT_GetEntryStringArray(NT_Entry entry, unsigned long long* last_change, size_t* arr_size); -/* Set Default Values */ +/** @} */ + +/** + * @defgroup SetDefault Set Default Values + * @{ + */ /** Set Default Entry Boolean. * Sets the default for the specified key to be a boolean. * If key exists with same type, does not set value. Otherwise * sets value to the default. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param default_boolean value to be set if name does not exist * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryBoolean(const char* name, size_t name_len, - int default_boolean); +NT_Bool NT_SetDefaultEntryBoolean(NT_Entry entry, unsigned long long time, + NT_Bool default_boolean); /** Set Default Entry Double. * Sets the default for the specified key. * If key exists with same type, does not set value. Otherwise * sets value to the default. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param default_double value to be set if name does not exist * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryDouble(const char* name, size_t name_len, - double default_double); +NT_Bool NT_SetDefaultEntryDouble(NT_Entry entry, unsigned long long time, + double default_double); /** Set Default Entry String. * Sets the default for the specified key. * If key exists with same type, does not set value. Otherwise * sets value to the default. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param default_value value to be set if name does not exist * @param default_len length of value * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryString(const char* name, size_t name_len, - const char* default_value, size_t default_len); +NT_Bool NT_SetDefaultEntryString(NT_Entry entry, unsigned long long time, + const char* default_value, size_t default_len); /** Set Default Entry Raw. * Sets the default for the specified key. * If key exists with same type, does not set value. Otherwise * sets value to the default. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param default_value value to be set if name does not exist * @param default_len length of value array * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryRaw(const char* name, size_t name_len, - const char* default_value, size_t default_len); +NT_Bool NT_SetDefaultEntryRaw(NT_Entry entry, unsigned long long time, + const char* default_value, size_t default_len); /** Set Default Entry Boolean Array. * Sets the default for the specified key. * If key exists with same type, does not set value. Otherwise * sets value to the default. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param default_value value to be set if name does not exist * @param default_size size of value array * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryBooleanArray(const char* name, size_t name_len, - const int* default_value, - size_t default_size); +NT_Bool NT_SetDefaultEntryBooleanArray(NT_Entry entry, unsigned long long time, + const int* default_value, + size_t default_size); /** Set Default Entry Double Array. * Sets the default for the specified key. * If key exists with same type, does not set value. Otherwise * sets value to the default. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param default_value value to be set if name does not exist * @param default_size size of value array * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryDoubleArray(const char* name, size_t name_len, - const double* default_value, - size_t default_size); +NT_Bool NT_SetDefaultEntryDoubleArray(NT_Entry entry, unsigned long long time, + const double* default_value, + size_t default_size); /** Set Default Entry String Array. * Sets the default for the specified key. * If key exists with same type, does not set value. Otherwise * sets value to the default. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param default_value value to be set if name does not exist * @param default_size size of value array * @return 0 on error (value not set), 1 on success */ -int NT_SetDefaultEntryStringArray(const char* name, size_t name_len, - const struct NT_String* default_value, - size_t default_size); +NT_Bool NT_SetDefaultEntryStringArray(NT_Entry entry, unsigned long long time, + const struct NT_String* default_value, + size_t default_size); -/* Entry Value Setters */ +/** @} */ + +/** + * @defgroup ValueSetters Entry Value Setters + * @{ + */ /** Set Entry Boolean * Sets an entry boolean. If the entry name is not currently assigned to a * boolean, returns error unless the force parameter is set. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param v_boolean boolean value to set * @param force 1 to force the entry to get overwritten, otherwise 0 * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryBoolean(const char* name, size_t name_len, int v_boolean, - int force); +NT_Bool NT_SetEntryBoolean(NT_Entry entry, unsigned long long time, + NT_Bool v_boolean, NT_Bool force); /** Set Entry Double * Sets an entry double. If the entry name is not currently assigned to a * double, returns error unless the force parameter is set. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param v_double double value to set * @param force 1 to force the entry to get overwritten, otherwise 0 * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryDouble(const char* name, size_t name_len, double v_double, - int force); +NT_Bool NT_SetEntryDouble(NT_Entry entry, unsigned long long time, + double v_double, NT_Bool force); /** Set Entry String * Sets an entry string. If the entry name is not currently assigned to a * string, returns error unless the force parameter is set. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param str string to set (UTF-8 string) * @param str_len length of string to write in bytes * @param force 1 to force the entry to get overwritten, otherwise 0 * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryString(const char* name, size_t name_len, const char* str, - size_t str_len, int force); +NT_Bool NT_SetEntryString(NT_Entry entry, unsigned long long time, + const char* str, size_t str_len, NT_Bool force); /** Set Entry Raw * Sets the raw value of an entry. If the entry name is not currently assigned * to a raw value, returns error unless the force parameter is set. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param raw raw string to set (UTF-8 string) * @param raw_len length of raw string to write in bytes * @param force 1 to force the entry to get overwritten, otherwise 0 * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryRaw(const char* name, size_t name_len, const char* raw, - size_t raw_len, int force); +NT_Bool NT_SetEntryRaw(NT_Entry entry, unsigned long long time, const char* raw, + size_t raw_len, NT_Bool force); /** Set Entry Boolean Array * Sets an entry boolean array. If the entry name is not currently assigned to * a boolean array, returns error unless the force parameter is set. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param arr boolean array to write * @param size number of elements in the array * @param force 1 to force the entry to get overwritten, otherwise 0 * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryBooleanArray(const char* name, size_t name_len, const int* arr, - size_t size, int force); +NT_Bool NT_SetEntryBooleanArray(NT_Entry entry, unsigned long long time, + const int* arr, size_t size, NT_Bool force); /** Set Entry Double Array * Sets an entry double array. If the entry name is not currently assigned to * a double array, returns error unless the force parameter is set. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param arr double array to write * @param size number of elements in the array * @param force 1 to force the entry to get overwritten, otherwise 0 * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryDoubleArray(const char* name, size_t name_len, const double* arr, - size_t size, int force); +NT_Bool NT_SetEntryDoubleArray(NT_Entry entry, unsigned long long time, + const double* arr, size_t size, NT_Bool force); /** Set Entry String Array * Sets an entry string array. If the entry name is not currently assigned to * a string array, returns error unless the force parameter is set. * - * @param name entry name (UTF-8 string) - * @param name_len length of name in bytes + * @param entry entry handle + * @param time timestamp * @param arr NT_String array to write * @param size number of elements in the array * @param force 1 to force the entry to get overwritten, otherwise 0 * @return 0 on error (type mismatch), 1 on success */ -int NT_SetEntryStringArray(const char* name, size_t name_len, - const struct NT_String* arr, size_t size, int force); +NT_Bool NT_SetEntryStringArray(NT_Entry entry, unsigned long long time, + const struct NT_String* arr, size_t size, + NT_Bool force); + +/** @} */ +/** @} */ #ifdef __cplusplus } diff --git a/src/main/native/include/ntcore_cpp.h b/src/main/native/include/ntcore_cpp.h index 0d484e6..e7b1c43 100644 --- a/src/main/native/include/ntcore_cpp.h +++ b/src/main/native/include/ntcore_cpp.h @@ -12,12 +12,14 @@ #include #include #include +#include #include #include "llvm/ArrayRef.h" #include "llvm/StringRef.h" +#include "support/deprecated.h" -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" namespace nt { @@ -26,6 +28,9 @@ using llvm::StringRef; /** NetworkTables Entry Information */ struct EntryInfo { + /** Entry handle */ + NT_Entry entry; + /** Entry name */ std::string name; @@ -37,18 +42,54 @@ struct EntryInfo { /** Timestamp of last change to entry (type or value). */ unsigned long long last_change; + + friend void swap(EntryInfo& first, EntryInfo& second) { + using std::swap; + swap(first.entry, second.entry); + swap(first.name, second.name); + swap(first.type, second.type); + swap(first.flags, second.flags); + swap(first.last_change, second.last_change); + } }; /** NetworkTables Connection Information */ struct ConnectionInfo { + /** + * The remote identifier (as set on the remote node by + * NetworkTableInstance::SetNetworkIdentity() or nt::SetNetworkIdentity()). + */ std::string remote_id; + + /** The IP address of the remote node. */ std::string remote_ip; + + /** The port number of the remote node. */ unsigned int remote_port; + + /** + * The last time any update was received from the remote node (same scale as + * returned by nt::Now()). + */ unsigned long long last_update; + + /** + * The protocol version being used for this connection. This in protocol + * layer format, so 0x0200 = 2.0, 0x0300 = 3.0). + */ unsigned int protocol_version; + + friend void swap(ConnectionInfo& first, ConnectionInfo& second) { + using std::swap; + swap(first.remote_id, second.remote_id); + swap(first.remote_ip, second.remote_ip); + swap(first.remote_port, second.remote_port); + swap(first.last_update, second.last_update); + swap(first.protocol_version, second.protocol_version); + } }; -/** NetworkTables RPC Parameter Definition */ +/** NetworkTables RPC Version 1 Definition Parameter */ struct RpcParamDef { RpcParamDef() = default; RpcParamDef(StringRef name_, std::shared_ptr def_value_) @@ -58,7 +99,7 @@ struct RpcParamDef { std::shared_ptr def_value; }; -/** NetworkTables RPC Result Definition */ +/** NetworkTables RPC Version 1 Definition Result */ struct RpcResultDef { RpcResultDef() = default; RpcResultDef(StringRef name_, NT_Type type_) : name(name_), type(type_) {} @@ -67,7 +108,7 @@ struct RpcResultDef { NT_Type type; }; -/** NetworkTables RPC Definition */ +/** NetworkTables RPC Version 1 Definition */ struct RpcDefinition { unsigned int version; std::string name; @@ -75,28 +116,264 @@ struct RpcDefinition { std::vector results; }; -/** NetworkTables RPC Call Data */ -struct RpcCallInfo { - unsigned int rpc_id; - unsigned int call_uid; +/** NetworkTables Remote Procedure Call (Server Side) */ +class RpcAnswer { + public: + RpcAnswer() : entry(0), call(0) {} + RpcAnswer(NT_Entry entry_, NT_RpcCall call_, StringRef name_, + StringRef params_, const ConnectionInfo& conn_) + : entry(entry_), call(call_), name(name_), params(params_), conn(conn_) {} + + /** Entry handle. */ + NT_Entry entry; + + /** Call handle. */ + NT_RpcCall call; + + /** Entry name. */ std::string name; + + /** Call raw parameters. */ std::string params; + + /** Connection that called the RPC. */ + ConnectionInfo conn; + + /** + * Determines if the native handle is valid. + * @return True if the native handle is valid, false otherwise. + */ + explicit operator bool() const { return call != 0; } + + /** + * Post RPC response (return value) for a polled RPC. + * @param result result raw data that will be provided to remote caller + */ + void PostResponse(StringRef result) const; + + friend void swap(RpcAnswer& first, RpcAnswer& second) { + using std::swap; + swap(first.entry, second.entry); + swap(first.call, second.call); + swap(first.name, second.name); + swap(first.params, second.params); + swap(first.conn, second.conn); + } +}; + +/** NetworkTables Entry Notification */ +class EntryNotification { + public: + EntryNotification() : listener(0), entry(0) {} + EntryNotification(NT_EntryListener listener_, NT_Entry entry_, + llvm::StringRef name_, std::shared_ptr value_, + unsigned int flags_) + : listener(listener_), + entry(entry_), + name(name_), + value(value_), + flags(flags_) {} + + /** Listener that was triggered. */ + NT_EntryListener listener; + + /** Entry handle. */ + NT_Entry entry; + + /** Entry name. */ + std::string name; + + /** The new value. */ + std::shared_ptr value; + + /** + * Update flags. For example, NT_NOTIFY_NEW if the key did not previously + * exist. + */ + unsigned int flags; + + friend void swap(EntryNotification& first, EntryNotification& second) { + using std::swap; + swap(first.listener, second.listener); + swap(first.entry, second.entry); + swap(first.name, second.name); + swap(first.value, second.value); + swap(first.flags, second.flags); + } }; -/* - * Table Functions +/** NetworkTables Connection Notification */ +class ConnectionNotification { + public: + ConnectionNotification() : listener(0), connected(false) {} + ConnectionNotification(NT_ConnectionListener listener_, bool connected_, + const ConnectionInfo& conn_) + : listener(listener_), connected(connected_), conn(conn_) {} + + /** Listener that was triggered. */ + NT_ConnectionListener listener; + + /** True if event is due to connection being established. */ + bool connected = false; + + /** Connection info. */ + ConnectionInfo conn; + + friend void swap(ConnectionNotification& first, + ConnectionNotification& second) { + using std::swap; + swap(first.listener, second.listener); + swap(first.connected, second.connected); + swap(first.conn, second.conn); + } +}; + +/** NetworkTables log message. */ +class LogMessage { + public: + LogMessage() : logger(0), level(0), filename(""), line(0) {} + LogMessage(NT_Logger logger_, unsigned int level_, const char* filename_, + unsigned int line_, llvm::StringRef message_) + : logger(logger_), + level(level_), + filename(filename_), + line(line_), + message(message_) {} + + /** The logger that generated the message. */ + NT_Logger logger; + + /** Log level of the message. See NT_LogLevel. */ + unsigned int level; + + /** The filename of the source file that generated the message. */ + const char* filename; + + /** The line number in the source file that generated the message. */ + unsigned int line; + + /** The message. */ + std::string message; + + friend void swap(LogMessage& first, LogMessage& second) { + using std::swap; + swap(first.logger, second.logger); + swap(first.level, second.level); + swap(first.filename, second.filename); + swap(first.line, second.line); + swap(first.message, second.message); + } +}; + +/** + * @defgroup InstanceFunctions Instance Functions + * @{ */ -/** Get Entry Value. +/** + * Get default instance. + * This is the instance used by non-handle-taking functions. + * @return Instance handle + */ +NT_Inst GetDefaultInstance(); + +/** + * Create an instance. + * @return Instance handle + */ +NT_Inst CreateInstance(); + +/** + * Destroy an instance. + * The default instance cannot be destroyed. + * @param inst Instance handle + */ +void DestroyInstance(NT_Inst inst); + +/** + * Get instance handle from another handle. + * @param handle entry/instance/etc. handle + * @return Instance handle + */ +NT_Inst GetInstanceFromHandle(NT_Handle handle); + +/** @} */ + +/** + * @defgroup TableFunctions Table Functions + * @{ + */ + +/** + * Get Entry Handle. + * @param inst instance handle + * @param name entry name (UTF-8 string) + * @return entry handle + */ +NT_Entry GetEntry(NT_Inst inst, StringRef name); + +/** + * Get Entry Handles. + * Returns an array of entry handles. The results are optionally + * filtered by string prefix and entry type to only return a subset of all + * entries. + * + * @param inst instance handle + * @param prefix entry name required prefix; only entries whose name + * starts with this string are returned + * @param types bitmask of NT_Type values; 0 is treated specially + * as a "don't care" + * @return Array of entry handles. + */ +std::vector GetEntries(NT_Inst inst, StringRef prefix, + unsigned int types); + +/** + * Gets the name of the specified entry. + * Returns an empty string if the handle is invalid. + * @param entry entry handle + * @return Entry name + */ +std::string GetEntryName(NT_Entry entry); + +/** + * Gets the type for the specified entry, or unassigned if non existent. + * @param entry entry handle + * @return Entry type + */ +NT_Type GetEntryType(NT_Entry entry); + +/** + * Gets the last time the entry was changed. + * Returns 0 if the handle is invalid. + * @param entry entry handle + * @return Entry last change time + */ +unsigned long long GetEntryLastChange(NT_Entry entry); + +/** + * Get Entry Value. * Returns copy of current entry value. * Note that one of the type options is "unassigned". * * @param name entry name (UTF-8 string) * @return entry value */ +WPI_DEPRECATED("use NT_Entry function instead") std::shared_ptr GetEntryValue(StringRef name); -/** Set Default Entry Value +/** + * Get Entry Value. + * Returns copy of current entry value. + * Note that one of the type options is "unassigned". + * + * @param entry entry handle + * @return entry value + */ +std::shared_ptr GetEntryValue(NT_Entry entry); + +/** + * Set Default Entry Value * Returns copy of current entry value if it exists. * Otherwise, sets passed in value, and returns set value. * Note that one of the type options is "unassigned". @@ -105,9 +382,23 @@ std::shared_ptr GetEntryValue(StringRef name); * @param value value to be set if name does not exist * @return False on error (value not set), True on success */ +WPI_DEPRECATED("use NT_Entry function instead") bool SetDefaultEntryValue(StringRef name, std::shared_ptr value); -/** Set Entry Value. +/** + * Set Default Entry Value + * Returns copy of current entry value if it exists. + * Otherwise, sets passed in value, and returns set value. + * Note that one of the type options is "unassigned". + * + * @param entry entry handle + * @param value value to be set if name does not exist + * @return False on error (value not set), True on success + */ +bool SetDefaultEntryValue(NT_Entry entry, std::shared_ptr value); + +/** + * Set Entry Value. * Sets new entry value. If type of new value differs from the type of the * currently stored entry, returns error and does not update value. * @@ -115,9 +406,22 @@ bool SetDefaultEntryValue(StringRef name, std::shared_ptr value); * @param value new entry value * @return False on error (type mismatch), True on success */ +WPI_DEPRECATED("use NT_Entry function instead") bool SetEntryValue(StringRef name, std::shared_ptr value); -/** Set Entry Type and Value. +/** + * Set Entry Value. + * Sets new entry value. If type of new value differs from the type of the + * currently stored entry, returns error and does not update value. + * + * @param entry entry handle + * @param value new entry value + * @return False on error (type mismatch), True on success + */ +bool SetEntryValue(NT_Entry entry, std::shared_ptr value); + +/** + * Set Entry Type and Value. * Sets new entry value. If type of new value differs from the type of the * currently stored entry, the currently stored entry type is overridden * (generally this will generate an Entry Assignment message). @@ -128,17 +432,55 @@ bool SetEntryValue(StringRef name, std::shared_ptr value); * @param name entry name (UTF-8 string) * @param value new entry value */ +WPI_DEPRECATED("use NT_Entry function instead") void SetEntryTypeValue(StringRef name, std::shared_ptr value); -/** Set Entry Flags. +/** + * Set Entry Type and Value. + * Sets new entry value. If type of new value differs from the type of the + * currently stored entry, the currently stored entry type is overridden + * (generally this will generate an Entry Assignment message). + * + * This is NOT the preferred method to update a value; generally + * SetEntryValue() should be used instead, with appropriate error handling. + * + * @param entry entry handle + * @param value new entry value + */ +void SetEntryTypeValue(NT_Entry entry, std::shared_ptr value); + +/** + * Set Entry Flags. + * @param name entry name (UTF-8 string) + * @param flags flags value (bitmask of NT_EntryFlags) */ +WPI_DEPRECATED("use NT_Entry function instead") void SetEntryFlags(StringRef name, unsigned int flags); -/** Get Entry Flags. +/** + * Set Entry Flags. + * @param entry entry handle + * @param flags flags value (bitmask of NT_EntryFlags) */ +void SetEntryFlags(NT_Entry entry, unsigned int flags); + +/** + * Get Entry Flags. + * @param name entry name (UTF-8 string) + * @return Flags value (bitmask of NT_EntryFlags) + */ +WPI_DEPRECATED("use NT_Entry function instead") unsigned int GetEntryFlags(StringRef name); -/** Delete Entry. +/** + * Get Entry Flags. + * @param entry entry handle + * @return Flags value (bitmask of NT_EntryFlags) + */ +unsigned int GetEntryFlags(NT_Entry entry); + +/** + * Delete Entry. * Deletes an entry. This is a new feature in version 3.0 of the protocol, * so this may not have an effect if any other node in the network is not * version 3.0 or newer. @@ -149,9 +491,25 @@ unsigned int GetEntryFlags(StringRef name); * * @param name entry name (UTF-8 string) */ +WPI_DEPRECATED("use NT_Entry function instead") void DeleteEntry(StringRef name); -/** Delete All Entries. +/** + * Delete Entry. + * Deletes an entry. This is a new feature in version 3.0 of the protocol, + * so this may not have an effect if any other node in the network is not + * version 3.0 or newer. + * + * Note: GetConnections() can be used to determine the protocol version + * of direct remote connection(s), but this is not sufficient to determine + * if all nodes in the network are version 3.0 or newer. + * + * @param entry entry handle + */ +void DeleteEntry(NT_Entry entry); + +/** + * Delete All Entries. * Deletes ALL table entries. This is a new feature in version 3.0 of the * so this may not have an effect if any other node in the network is not * version 3.0 or newer. @@ -160,9 +518,18 @@ void DeleteEntry(StringRef name); * of direct remote connection(s), but this is not sufficient to determine * if all nodes in the network are version 3.0 or newer. */ +WPI_DEPRECATED("use NT_Inst function instead") void DeleteAllEntries(); -/** Get Entry Information. +/** + * @copydoc DeleteAllEntries() + * + * @param inst instance handle + */ +void DeleteAllEntries(NT_Inst inst); + +/** + * Get Entry Information. * Returns an array of entry information (name, entry type, * and timestamp of last change to type/value). The results are optionally * filtered by string prefix and entry type to only return a subset of all @@ -174,122 +541,920 @@ void DeleteAllEntries(); * as a "don't care" * @return Array of entry information. */ +WPI_DEPRECATED("use NT_Inst function instead") std::vector GetEntryInfo(StringRef prefix, unsigned int types); -/** Flush Entries. - * Forces an immediate flush of all local entry changes to network. - * Normally this is done on a regularly scheduled interval (see - * NT_SetUpdateRate()). +/** + * @copydoc GetEntryInfo(StringRef, unsigned int) * - * Note: flushes are rate limited to avoid excessive network traffic. If - * the time between calls is too short, the flush will occur after the minimum - * time elapses (rather than immediately). + * @param inst instance handle */ -void Flush(); +std::vector GetEntryInfo(NT_Inst inst, StringRef prefix, + unsigned int types); -/* - * Callback Creation Functions +/** + * Get Entry Information. + * Returns information about an entry (name, entry type, + * and timestamp of last change to type/value). + * + * @param entry entry handle + * @return Entry information. */ +EntryInfo GetEntryInfo(NT_Entry entry); -void SetListenerOnStart(std::function on_start); -void SetListenerOnExit(std::function on_exit); +/** @} */ -typedef std::function value, unsigned int flags)> EntryListenerCallback; -typedef std::function callback, + unsigned int flags); + +/** + * Add a listener for a single entry. + * + * @param entry entry handle + * @param callback listener to add + * @param flags NotifyKind bitmask + * @return Listener handle + */ +NT_EntryListener AddEntryListener( + NT_Entry entry, + std::function callback, + unsigned int flags); + +/** + * Create a entry listener poller. + * A poller provides a single queue of poll events. Events linked to this + * poller (using AddPolledEntryListener()) will be stored in the queue and + * must be collected by calling PollEntryListener(). + * The returned handle must be destroyed with DestroyEntryListenerPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_EntryListenerPoller CreateEntryListenerPoller(NT_Inst inst); + +/** + * Destroy a entry listener poller. This will abort any blocked polling + * call and prevent additional events from being generated for this poller. + * @param poller poller handle + */ +void DestroyEntryListenerPoller(NT_EntryListenerPoller poller); + +/** + * Create a polled entry listener. + * The caller is responsible for calling PollEntryListener() to poll. + * @param poller poller handle + * @param prefix UTF-8 string prefix + * @param flags NotifyKind bitmask + * @return Listener handle + */ +NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller, + StringRef prefix, unsigned int flags); + +/** + * Create a polled entry listener. + * The caller is responsible for calling PollEntryListener() to poll. + * @param poller poller handle + * @param prefix UTF-8 string prefix + * @param flags NotifyKind bitmask + * @return Listener handle + */ +NT_EntryListener AddPolledEntryListener(NT_EntryListenerPoller poller, + NT_Entry entry, unsigned int flags); + +/** + * Get the next entry listener event. This blocks until the next event occurs. + * This is intended to be used with AddPolledEntryListener(); entry listeners + * created using AddEntryListener() will not be serviced through this function. + * @param poller poller handle + * @return Information on the entry listener events. Only returns empty if an + * error occurred (e.g. the instance was invalid or is shutting down). + */ +std::vector PollEntryListener(NT_EntryListenerPoller poller); + +/** + * Get the next entry listener event. This blocks until the next event occurs + * or it times out. This is intended to be used with AddPolledEntryListener(); + * entry listeners created using AddEntryListener() will not be serviced + * through this function. + * @param poller poller handle + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Information on the entry listener events. If empty is returned and + * and timed_out is also false, an error occurred (e.g. the instance + * was invalid or is shutting down). + */ +std::vector PollEntryListener(NT_EntryListenerPoller poller, + double timeout, + bool* timed_out); + +/** + * Cancel a PollEntryListener call. This wakes up a call to + * PollEntryListener for this poller and causes it to immediately return + * an empty array. + * @param poller poller handle + */ +void CancelPollEntryListener(NT_EntryListenerPoller poller); + +/** + * Remove an entry listener. + * @param entry_listener Listener handle to remove + */ +void RemoveEntryListener(NT_EntryListener entry_listener); + +/** + * Wait for the entry listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the entry listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +bool WaitForEntryListenerQueue(NT_Inst inst, double timeout); + +/** @} */ + +/** + * @defgroup ConnectionListenerFunctions Connection Listener Functions + * @{ + */ + +/** + * Connection listener callback function. + * Called when a network connection is made or lost. + * + * @param conn_listener connection listener handle returned by callback + * creation function + * @param connected true if event is due to connection being established + * @param conn connection info + */ +typedef std::function ConnectionListenerCallback; -unsigned int AddEntryListener(StringRef prefix, EntryListenerCallback callback, - unsigned int flags); -void RemoveEntryListener(unsigned int entry_listener_uid); -unsigned int AddConnectionListener(ConnectionListenerCallback callback, - bool immediate_notify); -void RemoveConnectionListener(unsigned int conn_listener_uid); +/** + * Add a connection listener. + * + * @param callback listener to add + * @param immediate_notify notify listener of all existing connections + * @return Listener handle + */ +WPI_DEPRECATED("use NT_Inst function instead") +NT_ConnectionListener AddConnectionListener(ConnectionListenerCallback callback, + bool immediate_notify); + +/** + * @copydoc AddConnectionListener(ConnectionListenerCallback, bool) + * @param inst instance handle + */ +NT_ConnectionListener AddConnectionListener( + NT_Inst inst, + std::function callback, + bool immediate_notify); + +/** + * Create a connection listener poller. + * A poller provides a single queue of poll events. Events linked to this + * poller (using AddPolledConnectionListener()) will be stored in the queue and + * must be collected by calling PollConnectionListener(). + * The returned handle must be destroyed with DestroyConnectionListenerPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_ConnectionListenerPoller CreateConnectionListenerPoller(NT_Inst inst); + +/** + * Destroy a connection listener poller. This will abort any blocked polling + * call and prevent additional events from being generated for this poller. + * @param poller poller handle + */ +void DestroyConnectionListenerPoller(NT_ConnectionListenerPoller poller); + +/** + * Create a polled connection listener. + * The caller is responsible for calling PollConnectionListener() to poll. + * @param poller poller handle + * @param immediate_notify notify listener of all existing connections + */ +NT_ConnectionListener AddPolledConnectionListener( + NT_ConnectionListenerPoller poller, bool immediate_notify); + +/** + * Get the next connection event. This blocks until the next connect or + * disconnect occurs. This is intended to be used with + * AddPolledConnectionListener(); connection listeners created using + * AddConnectionListener() will not be serviced through this function. + * @param poller poller handle + * @return Information on the connection events. Only returns empty if an + * error occurred (e.g. the instance was invalid or is shutting down). + */ +std::vector PollConnectionListener( + NT_ConnectionListenerPoller poller); + +/** + * Get the next connection event. This blocks until the next connect or + * disconnect occurs or it times out. This is intended to be used with + * AddPolledConnectionListener(); connection listeners created using + * AddConnectionListener() will not be serviced through this function. + * @param poller poller handle + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Information on the connection events. If empty is returned and + * timed_out is also false, an error occurred (e.g. the instance was + * invalid or is shutting down). + */ +std::vector PollConnectionListener( + NT_ConnectionListenerPoller poller, double timeout, bool* timed_out); + +/** + * Cancel a PollConnectionListener call. This wakes up a call to + * PollConnectionListener for this poller and causes it to immediately return + * an empty array. + * @param poller poller handle + */ +void CancelPollConnectionListener(NT_ConnectionListenerPoller poller); + +/** + * Remove a connection listener. + * @param conn_listener Listener handle to remove + */ +void RemoveConnectionListener(NT_ConnectionListener conn_listener); + +/** + * Wait for the connection listener queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the connection listener + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +bool WaitForConnectionListenerQueue(NT_Inst inst, double timeout); + +/** @} */ + +/** + * @defgroup RpcFunctions Remote Procedure Call Functions + * @{ + */ + +/** + * Create a callback-based RPC entry point. Only valid to use on the server. + * The callback function will be called when the RPC is called. + * @param entry entry handle of RPC entry + * @param def RPC definition + * @param callback callback function; note the callback function must call + * PostRpcResponse() to provide a response to the call + */ +void CreateRpc(NT_Entry entry, StringRef def, + std::function callback); + +/** + * Create a RPC call poller. Only valid to use on the server. + * A poller provides a single queue of poll events. Events linked to this + * poller (using CreatePolledRpc()) will be stored in the queue and must be + * collected by calling PollRpc(). + * The returned handle must be destroyed with DestroyRpcCallPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_RpcCallPoller CreateRpcCallPoller(NT_Inst inst); + +/** + * Destroy a RPC call poller. This will abort any blocked polling call and + * prevent additional events from being generated for this poller. + * @param poller poller handle + */ +void DestroyRpcCallPoller(NT_RpcCallPoller poller); -bool NotifierDestroyed(); +/** + * Create a polled RPC entry point. Only valid to use on the server. + * The caller is responsible for calling PollRpc() to poll for servicing + * incoming RPC calls. + * @param entry entry handle of RPC entry + * @param def RPC definition + * @param poller poller handle + */ +void CreatePolledRpc(NT_Entry entry, StringRef def, NT_RpcCallPoller poller); -/* - * Remote Procedure Call Functions +/** + * Get the next incoming RPC call. This blocks until the next incoming RPC + * call is received. This is intended to be used with CreatePolledRpc(); + * RPC calls created using CreateRpc() will not be serviced through this + * function. Upon successful return, PostRpcResponse() must be called to + * send the return value to the caller. + * @param poller poller handle + * @return Information on the next RPC calls. Only returns empty if an error + * occurred (e.g. the instance was invalid or is shutting down). */ +std::vector PollRpc(NT_RpcCallPoller poller); -#if defined(_MSC_VER) && _MSC_VER < 1900 -const double kTimeout_Indefinite = -1; -#else -constexpr double kTimeout_Indefinite = -1; -#endif +/** + * Get the next incoming RPC call. This blocks until the next incoming RPC + * call is received or it times out. This is intended to be used with + * CreatePolledRpc(); RPC calls created using CreateRpc() will not be + * serviced through this function. Upon successful return, + * PostRpcResponse() must be called to send the return value to the caller. + * @param poller poller handle + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Information on the next RPC calls. If empty and timed_out is also + * false, an error occurred (e.g. the instance was invalid or is + * shutting down). + */ +std::vector PollRpc(NT_RpcCallPoller poller, double timeout, + bool* timed_out); -void SetRpcServerOnStart(std::function on_start); -void SetRpcServerOnExit(std::function on_exit); +/** + * Cancel a PollRpc call. This wakes up a call to PollRpc for this poller + * and causes it to immediately return an empty array. + * @param poller poller handle + */ +void CancelPollRpc(NT_RpcCallPoller poller); -typedef std::function - RpcCallback; +/** + * Wait for the incoming RPC call queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the RPC call + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +bool WaitForRpcCallQueue(NT_Inst inst, double timeout); -void CreateRpc(StringRef name, StringRef def, RpcCallback callback); -void CreatePolledRpc(StringRef name, StringRef def); +/** + * Post RPC response (return value) for a polled RPC. + * The rpc and call parameters should come from the RpcAnswer returned + * by PollRpc(). + * @param entry entry handle of RPC entry (from RpcAnswer) + * @param call RPC call handle (from RpcAnswer) + * @param result result raw data that will be provided to remote caller + */ +void PostRpcResponse(NT_Entry entry, NT_RpcCall call, StringRef result); -bool PollRpc(bool blocking, RpcCallInfo* call_info); -bool PollRpc(bool blocking, double time_out, RpcCallInfo* call_info); -void PostRpcResponse(unsigned int rpc_id, unsigned int call_uid, - StringRef result); +/** + * Call a RPC function. May be used on either the client or server. + * This function is non-blocking. Either GetRpcResult() or + * CancelRpcResult() must be called to either get or ignore the result of + * the call. + * @param entry entry handle of RPC entry + * @param params parameter + * @return RPC call handle (for use with GetRpcResult() or + * CancelRpcResult()). + */ +NT_RpcCall CallRpc(NT_Entry entry, StringRef params); -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, - std::string* result); -void CancelBlockingRpcResult(unsigned int call_uid); +/** + * Get the result (return value) of a RPC call. This function blocks until + * the result is received. + * @param entry entry handle of RPC entry + * @param call RPC call handle returned by CallRpc() + * @param result received result (output) + * @return False on error, true otherwise. + */ +bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result); + +/** + * Get the result (return value) of a RPC call. This function blocks until + * the result is received or it times out. + * @param entry entry handle of RPC entry + * @param call RPC call handle returned by CallRpc() + * @param result received result (output) + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return False on error or timeout, true otherwise. + */ +bool GetRpcResult(NT_Entry entry, NT_RpcCall call, std::string* result, + double timeout, bool* timed_out); +/** + * Ignore the result of a RPC call. This function is non-blocking. + * @param entry entry handle of RPC entry + * @param call RPC call handle returned by CallRpc() + */ +void CancelRpcResult(NT_Entry entry, NT_RpcCall call); + +/** + * Pack a RPC version 1 definition. + * @param def RPC version 1 definition + * @return Raw packed bytes. + */ std::string PackRpcDefinition(const RpcDefinition& def); + +/** + * Unpack a RPC version 1 definition. This can be used for introspection or + * validation. + * @param packed raw packed bytes + * @param def RPC version 1 definition (output) + * @return True if successfully unpacked, false otherwise. + */ bool UnpackRpcDefinition(StringRef packed, RpcDefinition* def); + +/** + * Pack RPC values as required for RPC version 1 definition messages. + * @param values array of values to pack + * @return Raw packed bytes. + */ std::string PackRpcValues(ArrayRef> values); + +/** + * Unpack RPC values as required for RPC version 1 definition messages. + * @param packed raw packed bytes + * @param types array of data types (as provided in the RPC definition) + * @return Array of values. + */ std::vector> UnpackRpcValues(StringRef packed, ArrayRef types); -/* - * Client/Server Functions +/** @} */ + +/** + * @defgroup NetworkFunctions Client/Server Functions + * @{ */ + +/** + * Set the network identity of this node. + * This is the name used during the initial connection handshake, and is + * visible through ConnectionInfo on the remote node. + * @param name identity to advertise + */ +WPI_DEPRECATED("use NT_Inst function instead") void SetNetworkIdentity(StringRef name); + +/** + * @copydoc SetNetworkIdentity(StringRef) + * @param inst instance handle + */ +void SetNetworkIdentity(NT_Inst inst, StringRef name); + +/** + * Get the current network mode. + * @return Bitmask of NT_NetworkMode. + */ +WPI_DEPRECATED("use NT_Inst function instead") unsigned int GetNetworkMode(); + +/** + * Get the current network mode. + * @param inst instance handle + * @return Bitmask of NT_NetworkMode. + */ +unsigned int GetNetworkMode(NT_Inst inst); + +/** + * Starts a server using the specified filename, listening address, and port. + * + * @param persist_filename the name of the persist file to use (UTF-8 string, + * null terminated) + * @param listen_address the address to listen on, or null to listen on any + * address. (UTF-8 string, null terminated) + * @param port port to communicate over. + */ +WPI_DEPRECATED("use NT_Inst function instead") void StartServer(StringRef persist_filename, const char* listen_address, unsigned int port); + +/** + * @copydoc StartServer(StringRef, const char*, unsigned int) + * @param inst instance handle + */ +void StartServer(NT_Inst inst, StringRef persist_filename, + const char* listen_address, unsigned int port); + +/** + * Stops the server if it is running. + */ +WPI_DEPRECATED("use NT_Inst function instead") void StopServer(); + +/** + * @copydoc StopServer() + * @param inst instance handle + */ +void StopServer(NT_Inst inst); + +/** + * Starts a client. Use SetServer to set the server name and port. + */ +WPI_DEPRECATED("use NT_Inst function instead") void StartClient(); + +/** + * Starts a client using the specified server and port + * + * @param server_name server name (UTF-8 string, null terminated) + * @param port port to communicate over + */ +WPI_DEPRECATED("use NT_Inst function instead") void StartClient(const char* server_name, unsigned int port); + +/** + * Starts a client using the specified (server, port) combinations. The + * client will attempt to connect to each server in round robin fashion. + * + * @param servers array of server name and port pairs + */ +WPI_DEPRECATED("use NT_Inst function instead") void StartClient(ArrayRef> servers); + +/** + * @copydoc StartClient() + * @param inst instance handle + */ +void StartClient(NT_Inst inst); + +/** + * @copydoc StartClient(const char*, unsigned int) + * @param inst instance handle + */ +void StartClient(NT_Inst inst, const char* server_name, unsigned int port); + +/** + * @copydoc StartClient(ArrayRef>) + * @param inst instance handle + */ +void StartClient(NT_Inst inst, + ArrayRef> servers); + +/** + * Starts a client using commonly known robot addresses for the specified + * team. + * + * @param inst instance handle + * @param team team number + * @param port port to communicate over + */ +void StartClientTeam(NT_Inst inst, unsigned int team, unsigned int port); + +/** + * Stops the client if it is running. + */ +WPI_DEPRECATED("use NT_Inst function instead") void StopClient(); + +/** + * @copydoc StopClient() + * @param inst instance handle + */ +void StopClient(NT_Inst inst); + +/** + * Sets server address and port for client (without restarting client). + * + * @param server_name server name (UTF-8 string, null terminated) + * @param port port to communicate over + */ +WPI_DEPRECATED("use NT_Inst function instead") void SetServer(const char* server_name, unsigned int port); + +/** + * Sets server addresses for client (without restarting client). + * The client will attempt to connect to each server in round robin fashion. + * + * @param servers array of server name and port pairs + */ +WPI_DEPRECATED("use NT_Inst function instead") void SetServer(ArrayRef> servers); + +/** + * @copydoc SetServer(const char*, unsigned int) + * @param inst instance handle + */ +void SetServer(NT_Inst inst, const char* server_name, unsigned int port); + +/** + * @copydoc SetServer(ArrayRef>) + * @param inst instance handle + */ +void SetServer(NT_Inst inst, + ArrayRef> servers); + +/** + * Sets server addresses and port for client (without restarting client). + * Connects using commonly known robot addresses for the specified team. + * + * @param inst instance handle + * @param team team number + * @param port port to communicate over + */ +void SetServerTeam(NT_Inst inst, unsigned int team, unsigned int port); + +/** + * Starts requesting server address from Driver Station. + * This connects to the Driver Station running on localhost to obtain the + * server IP address. + * + * @param port server port to use in combination with IP from DS + */ +WPI_DEPRECATED("use NT_Inst function instead") void StartDSClient(unsigned int port); + +/** + * @copydoc StartDSClient(unsigned int) + * @param inst instance handle + */ +void StartDSClient(NT_Inst inst, unsigned int port); + +/** Stops requesting server address from Driver Station. */ +WPI_DEPRECATED("use NT_Inst function instead") void StopDSClient(); + +/** + * @copydoc StopDSClient() + * @param inst instance handle + */ +void StopDSClient(NT_Inst inst); + +/** Stops the RPC server if it is running. */ +WPI_DEPRECATED("use NT_Inst function instead") void StopRpcServer(); -void StopNotifier(); + +/** + * Set the periodic update rate. + * Sets how frequently updates are sent to other nodes over the network. + * + * @param interval update interval in seconds (range 0.01 to 1.0) + */ +WPI_DEPRECATED("use NT_Inst function instead") void SetUpdateRate(double interval); + +/** + * @copydoc SetUpdateRate(double) + * @param inst instance handle + */ +void SetUpdateRate(NT_Inst inst, double interval); + +/** + * Flush Entries. + * Forces an immediate flush of all local entry changes to network. + * Normally this is done on a regularly scheduled interval (see + * SetUpdateRate()). + * + * Note: flushes are rate limited to avoid excessive network traffic. If + * the time between calls is too short, the flush will occur after the minimum + * time elapses (rather than immediately). + */ +WPI_DEPRECATED("use NT_Inst function instead") +void Flush(); + +/** + * @copydoc Flush() + * + * @param inst instance handle + */ +void Flush(NT_Inst inst); + +/** + * Get information on the currently established network connections. + * If operating as a client, this will return either zero or one values. + * + * @return array of connection information + */ +WPI_DEPRECATED("use NT_Inst function instead") std::vector GetConnections(); -/* - * Persistent Functions +/** + * @copydoc GetConnections() + * @param inst instance handle */ -/* return error string, or nullptr if successful */ +std::vector GetConnections(NT_Inst inst); + +/** + * Return whether or not the instance is connected to another node. + * @param inst instance handle + * @return True if connected. + */ +bool IsConnected(NT_Inst inst); + +/** @} */ + +/** + * @defgroup PersistentFunctions Persistent Functions + * @{ + */ + +/** + * Save persistent values to a file. The server automatically does this, + * but this function provides a way to save persistent values in the same + * format to a file on either a client or a server. + * @param filename filename + * @return error string, or nullptr if successful + */ +WPI_DEPRECATED("use NT_Inst function instead") const char* SavePersistent(StringRef filename); + +/** + * @copydoc SavePersistent(StringRef) + * @param inst instance handle + */ +const char* SavePersistent(NT_Inst inst, StringRef filename); + +/** + * Load persistent values from a file. The server automatically does this + * at startup, but this function provides a way to restore persistent values + * in the same format from a file at any time on either a client or a server. + * @param filename filename + * @param warn callback function for warnings + * @return error string, or nullptr if successful + */ +WPI_DEPRECATED("use NT_Inst function instead") const char* LoadPersistent( StringRef filename, std::function warn); -/* - * Utility Functions +/** + * @copydoc LoadPersistent(StringRef, std::function) + * @param inst instance handle */ +const char* LoadPersistent( + NT_Inst inst, StringRef filename, + std::function warn); -/* timestamp */ +/** @} */ + +/** + * @defgroup UtilityFunctions Utility Functions + * @{ + */ + +/** + * Returns monotonic current time in 100 ns increments. + * This is the same time base used for entry and connection timestamps. + * This function is a compatibility wrapper around wpi::Now(). + * @return Timestamp + */ unsigned long long Now(); -/* logging */ +/** @} */ + +/** + * @defgroup LoggerFunctions Logger Functions + * @{ + */ + +/** + * Log function. + * @param level log level of the message (see NT_LogLevel) + * @param file origin source filename + * @param line origin source line number + * @param msg message + */ typedef std::function LogFunc; + +/** + * Set logger callback function. By default, log messages are sent to stderr; + * this function changes the log level and sends log messages to the provided + * callback function instead. The callback function will only be called for + * log messages with level greater than or equal to min_level; messages lower + * than this level will be silently ignored. + * + * @param func log callback function + * @param min_level minimum log level + */ +WPI_DEPRECATED("use NT_Inst function instead") void SetLogger(LogFunc func, unsigned int min_level); +/** + * Add logger callback function. By default, log messages are sent to stderr; + * this function sends log messages to the provided callback function instead. + * The callback function will only be called for log messages with level + * greater than or equal to min_level and less than or equal to max_level; + * messages outside this range will be silently ignored. + * + * @param inst instance handle + * @param func log callback function + * @param min_level minimum log level + * @param max_level maximum log level + * @return Logger handle + */ +NT_Logger AddLogger(NT_Inst inst, + std::function func, + unsigned int min_level, unsigned int max_level); + +/** + * Create a log poller. A poller provides a single queue of poll events. + * The returned handle must be destroyed with DestroyLoggerPoller(). + * @param inst instance handle + * @return poller handle + */ +NT_LoggerPoller CreateLoggerPoller(NT_Inst inst); + +/** + * Destroy a log poller. This will abort any blocked polling call and prevent + * additional events from being generated for this poller. + * @param poller poller handle + */ +void DestroyLoggerPoller(NT_LoggerPoller poller); + +/** + * Set the log level for a log poller. Events will only be generated for + * log messages with level greater than or equal to min_level and less than or + * equal to max_level; messages outside this range will be silently ignored. + * @param poller poller handle + * @param min_level minimum log level + * @param max_level maximum log level + * @return Logger handle + */ +NT_Logger AddPolledLogger(NT_LoggerPoller poller, unsigned int min_level, + unsigned int max_level); + +/** + * Get the next log event. This blocks until the next log occurs. + * @param poller poller handle + * @return Information on the log events. Only returns empty if an error + * occurred (e.g. the instance was invalid or is shutting down). + */ +std::vector PollLogger(NT_LoggerPoller poller); + +/** + * Get the next log event. This blocks until the next log occurs or it times + * out. + * @param poller poller handle + * @param timeout timeout, in seconds + * @param timed_out true if the timeout period elapsed (output) + * @return Information on the log events. If empty is returned and timed_out + * is also false, an error occurred (e.g. the instance was invalid or + * is shutting down). + */ +std::vector PollLogger(NT_LoggerPoller poller, double timeout, + bool* timed_out); + +/** + * Cancel a PollLogger call. This wakes up a call to PollLogger for this + * poller and causes it to immediately return an empty array. + * @param poller poller handle + */ +void CancelPollLogger(NT_LoggerPoller poller); + +/** + * Remove a logger. + * @param logger Logger handle to remove + */ +void RemoveLogger(NT_Logger logger); + +/** + * Wait for the incoming log event queue to be empty. This is primarily useful + * for deterministic testing. This blocks until either the log event + * queue is empty (e.g. there are no more events that need to be passed along + * to callbacks or poll queues) or the timeout expires. + * @param inst instance handle + * @param timeout timeout, in seconds. Set to 0 for non-blocking behavior, + * or a negative value to block indefinitely + * @return False if timed out, otherwise true. + */ +bool WaitForLoggerQueue(NT_Inst inst, double timeout); + +/** @} */ + +inline void RpcAnswer::PostResponse(StringRef result) const { + PostRpcResponse(entry, call, result); +} + } // namespace nt #endif /* NTCORE_CPP_H_ */ diff --git a/src/main/native/include/tables/ITable.h b/src/main/native/include/tables/ITable.h index 8b3fe43..b9a79bd 100644 --- a/src/main/native/include/tables/ITable.h +++ b/src/main/native/include/tables/ITable.h @@ -11,15 +11,19 @@ #include #include "llvm/StringRef.h" -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" #include "support/deprecated.h" +namespace nt { +class NetworkTable; +} // namespace nt + class ITableListener; /** * A table whose values can be read and written to */ -class ITable { +class WPI_DEPRECATED("Use NetworkTable directly") ITable { public: /** * Determines whether the given key is in this table. @@ -45,7 +49,7 @@ class ITable { * @param key the name of the table relative to this one * @return a sub table relative to this one */ - virtual std::shared_ptr GetSubTable(llvm::StringRef key) const = 0; + virtual std::shared_ptr GetSubTable(llvm::StringRef key) const = 0; /** * @param types bitmask of types; 0 is treated as a "don't care". diff --git a/src/main/native/include/tables/ITableListener.h b/src/main/native/include/tables/ITableListener.h index 76e0715..818b500 100644 --- a/src/main/native/include/tables/ITableListener.h +++ b/src/main/native/include/tables/ITableListener.h @@ -8,14 +8,22 @@ #include #include "llvm/StringRef.h" -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" +#include "support/deprecated.h" + +#ifdef __GNUC__ +#pragma GCC diagnostic push +#pragma GCC diagnostic ignored "-Wdeprecated-declarations" +#endif class ITable; /** * A listener that listens to changes in values in a {@link ITable} */ -class ITableListener { +class WPI_DEPRECATED( + "Use EntryListener, TableEntryListener, or TableListener as appropriate") + ITableListener { public: virtual ~ITableListener() = default; /** @@ -44,4 +52,8 @@ class ITableListener { unsigned int flags); }; +#ifdef __GNUC__ +#pragma GCC diagnostic pop +#endif + #endif /* ITABLELISTENER_H_ */ diff --git a/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java b/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java new file mode 100644 index 0000000..a406013 --- /dev/null +++ b/src/test/java/edu/wpi/first/networktables/ConnectionListenerTest.java @@ -0,0 +1,130 @@ +package edu.wpi.first.networktables; + +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; + +public class ConnectionListenerTest extends TestCase { + + NetworkTableInstance serverInst; + NetworkTableInstance clientInst; + + @Override + protected void setUp() throws Exception { + serverInst = NetworkTableInstance.create(); + serverInst.setNetworkIdentity("server"); + + clientInst = NetworkTableInstance.create(); + clientInst.setNetworkIdentity("client"); + } + + @Override + protected void tearDown() throws Exception { + clientInst.free(); + serverInst.free(); + } + + private void connect() { + serverInst.startServer("connectionlistenertest.ini", "127.0.0.1", 10000); + clientInst.startClient("127.0.0.1", 10000); + + // wait for client to report it's started, then wait another 0.1 sec + try { + while ((clientInst.getNetworkMode() & NetworkTableInstance.kNetModeStarting) != 0) { + Thread.sleep(100); + } + Thread.sleep(100); + } catch (InterruptedException ex) { + fail("interrupted while waiting for client to start"); + } + } + + public void testJNI() { + // set up the poller + int poller = NetworkTablesJNI.createConnectionListenerPoller(serverInst.getHandle()); + assertTrue("bad poller handle", poller != 0); + int handle = NetworkTablesJNI.addPolledConnectionListener(poller, false); + assertTrue("bad listener handle", handle != 0); + + // trigger a connect event + connect(); + + // get the event + assertTrue(serverInst.waitForConnectionListenerQueue(1.0)); + ConnectionNotification[] events = null; + try { + events = NetworkTablesJNI.pollConnectionListenerTimeout(serverInst, poller, 0.0); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + fail("unexpected interrupted exception" + ex); + } + + assertNotNull(events); + assertEquals(events.length, 1); + assertEquals(handle, events[0].listener); + assertTrue(events[0].connected); + + // trigger a disconnect event + clientInst.stopClient(); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + fail("interrupted while waiting for client to stop"); + } + + // get the event + assertTrue(serverInst.waitForConnectionListenerQueue(1.0)); + try { + events = NetworkTablesJNI.pollConnectionListenerTimeout(serverInst, poller, 0.0); + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + fail("unexpected interrupted exception" + ex); + } + + assertNotNull(events); + assertEquals(events.length, 1); + assertEquals(handle, events[0].listener); + assertFalse(events[0].connected); + + } + + public void testThreaded() { + serverInst.startServer("connectionlistenertest.ini", "127.0.0.1", 10000); + List events = new ArrayList<>(); + int handle = serverInst.addConnectionListener((event) -> events.add(event), false); + + // trigger a connect event + clientInst.startClient("127.0.0.1", 10000); + + // wait for client to report it's started, then wait another 0.1 sec + try { + while ((clientInst.getNetworkMode() & NetworkTableInstance.kNetModeStarting) != 0) { + Thread.sleep(100); + } + Thread.sleep(100); + } catch (InterruptedException ex) { + fail("interrupted while waiting for client to start"); + } + assertTrue(serverInst.waitForConnectionListenerQueue(1.0)); + + // get the event + assertEquals(events.size(), 1); + assertEquals(handle, events.get(0).listener); + assertTrue(events.get(0).connected); + events.clear(); + + // trigger a disconnect event + clientInst.stopClient(); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + fail("interrupted while waiting for client to stop"); + } + + // get the event + assertTrue(serverInst.waitForConnectionListenerQueue(1.0)); + assertEquals(events.size(), 1); + assertEquals(handle, events.get(0).listener); + assertFalse(events.get(0).connected); + } +} diff --git a/src/test/java/edu/wpi/first/networktables/EntryListenerTest.java b/src/test/java/edu/wpi/first/networktables/EntryListenerTest.java new file mode 100644 index 0000000..6740d09 --- /dev/null +++ b/src/test/java/edu/wpi/first/networktables/EntryListenerTest.java @@ -0,0 +1,70 @@ +package edu.wpi.first.networktables; + +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; + +public class EntryListenerTest extends TestCase { + + NetworkTableInstance serverInst; + NetworkTableInstance clientInst; + + @Override + protected void setUp() throws Exception { + serverInst = NetworkTableInstance.create(); + serverInst.setNetworkIdentity("server"); + + clientInst = NetworkTableInstance.create(); + clientInst.setNetworkIdentity("client"); + } + + @Override + protected void tearDown() throws Exception { + clientInst.free(); + serverInst.free(); + } + + private void connect() { + serverInst.startServer("connectionlistenertest.ini", "127.0.0.1", 10000); + clientInst.startClient("127.0.0.1", 10000); + + // Use connection listener to ensure we've connected + int poller = NetworkTablesJNI.createConnectionListenerPoller(clientInst.getHandle()); + NetworkTablesJNI.addPolledConnectionListener(poller, false); + try { + if (NetworkTablesJNI.pollConnectionListenerTimeout(clientInst, poller, 1.0).length == 0) { + fail("client didn't connect to server"); + } + } catch (InterruptedException ex) { + Thread.currentThread().interrupt(); + fail("interrupted while waiting for server connection"); + } + } + + public void testPrefixNewRemote() { + connect(); + List events = new ArrayList<>(); + int handle = serverInst.addEntryListener("/foo", (event) -> events.add(event), + EntryListenerFlags.kNew); + + // Trigger an event + clientInst.getEntry("/foo/bar").setDouble(1.0); + clientInst.getEntry("/baz").setDouble(1.0); + clientInst.flush(); + try { + Thread.sleep(100); + } catch (InterruptedException ex) { + fail("interrupted while waiting for entries to update"); + } + + assertTrue(serverInst.waitForEntryListenerQueue(1.0)); + + // Check the event + assertEquals(events.size(), 1); + assertEquals(events.get(0).listener, handle); + assertEquals(events.get(0).getEntry(), serverInst.getEntry("/foo/bar")); + assertEquals(events.get(0).name, "/foo/bar"); + assertEquals(events.get(0).value, NetworkTableValue.makeDouble(1.0)); + assertEquals(events.get(0).flags, EntryListenerFlags.kNew); + } +} diff --git a/src/test/java/edu/wpi/first/networktables/JNITest.java b/src/test/java/edu/wpi/first/networktables/JNITest.java new file mode 100644 index 0000000..449ca06 --- /dev/null +++ b/src/test/java/edu/wpi/first/networktables/JNITest.java @@ -0,0 +1,12 @@ +package edu.wpi.first.networktables; + +import org.junit.Test; + +public class JNITest { + @Test + public void jniLinkTest() { + // Test to verify that the JNI test link works correctly. + int inst = NetworkTablesJNI.getDefaultInstance(); + NetworkTablesJNI.flush(inst); + } +} diff --git a/src/test/java/edu/wpi/first/networktables/LoggerTest.java b/src/test/java/edu/wpi/first/networktables/LoggerTest.java new file mode 100644 index 0000000..e6a3672 --- /dev/null +++ b/src/test/java/edu/wpi/first/networktables/LoggerTest.java @@ -0,0 +1,39 @@ +package edu.wpi.first.networktables; + +import java.util.ArrayList; +import java.util.List; +import junit.framework.TestCase; + +public class LoggerTest extends TestCase { + + NetworkTableInstance clientInst; + + @Override + protected void setUp() throws Exception { + clientInst = NetworkTableInstance.create(); + } + + @Override + protected void tearDown() throws Exception { + clientInst.free(); + } + + public void testLogger() { + List msgs = new ArrayList<>(); + clientInst.addLogger((msg) -> msgs.add(msg), LogMessage.kInfo, 100); + + clientInst.startClient("127.0.0.1", 10000); + + // wait for client to report it's started, then wait another 0.1 sec + try { + while ((clientInst.getNetworkMode() & NetworkTableInstance.kNetModeStarting) != 0) { + Thread.sleep(100); + } + Thread.sleep(100); + } catch (InterruptedException ex) { + fail("interrupted while waiting for client to start"); + } + + assertFalse(msgs.isEmpty()); + } +} diff --git a/src/test/java/edu/wpi/first/wpilibj/networktables/JNITest.java b/src/test/java/edu/wpi/first/wpilibj/networktables/JNITest.java deleted file mode 100644 index 52e1951..0000000 --- a/src/test/java/edu/wpi/first/wpilibj/networktables/JNITest.java +++ /dev/null @@ -1,11 +0,0 @@ -package edu.wpi.first.wpilibj.networktables; - -import org.junit.Test; - -public class JNITest { - @Test - public void jniLinkTest() { - // Test to verify that the JNI test link works correctly. - edu.wpi.first.wpilibj.networktables.NetworkTablesJNI.flush(); - } -} diff --git a/src/test/native/cpp/ConnectionListenerTest.cpp b/src/test/native/cpp/ConnectionListenerTest.cpp new file mode 100644 index 0000000..70710cc --- /dev/null +++ b/src/test/native/cpp/ConnectionListenerTest.cpp @@ -0,0 +1,109 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "ntcore_cpp.h" + +#include +#include + +#include "TestPrinters.h" +#include "gtest/gtest.h" + +class ConnectionListenerTest : public ::testing::Test { + public: + ConnectionListenerTest() + : server_inst(nt::CreateInstance()), client_inst(nt::CreateInstance()) { + nt::SetNetworkIdentity(server_inst, "server"); + nt::SetNetworkIdentity(client_inst, "client"); + } + + ~ConnectionListenerTest() override { + nt::DestroyInstance(server_inst); + nt::DestroyInstance(client_inst); + } + + void Connect(); + + protected: + NT_Inst server_inst; + NT_Inst client_inst; +}; + +void ConnectionListenerTest::Connect() { + nt::StartServer(server_inst, "connectionlistenertest.ini", "127.0.0.1", + 10000); + nt::StartClient(client_inst, "127.0.0.1", 10000); + + // wait for client to report it's started, then wait another 0.1 sec + while ((nt::GetNetworkMode(client_inst) & NT_NET_MODE_STARTING) != 0) + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); +} + +TEST_F(ConnectionListenerTest, Polled) { + // set up the poller + NT_ConnectionListenerPoller poller = + nt::CreateConnectionListenerPoller(server_inst); + ASSERT_NE(poller, 0u); + NT_ConnectionListener handle = nt::AddPolledConnectionListener(poller, false); + ASSERT_NE(handle, 0u); + + // trigger a connect event + Connect(); + + // get the event + ASSERT_TRUE(nt::WaitForConnectionListenerQueue(server_inst, 1.0)); + bool timed_out = false; + auto result = nt::PollConnectionListener(poller, 0.1, &timed_out); + EXPECT_FALSE(timed_out); + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(handle, result[0].listener); + EXPECT_TRUE(result[0].connected); + + // trigger a disconnect event + nt::StopClient(client_inst); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // get the event + ASSERT_TRUE(nt::WaitForConnectionListenerQueue(server_inst, 1.0)); + timed_out = false; + result = nt::PollConnectionListener(poller, 0.1, &timed_out); + EXPECT_FALSE(timed_out); + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(handle, result[0].listener); + EXPECT_FALSE(result[0].connected); + + // trigger a disconnect event +} + +TEST_F(ConnectionListenerTest, Threaded) { + std::vector result; + auto handle = nt::AddConnectionListener( + server_inst, + [&](const nt::ConnectionNotification& event) { result.push_back(event); }, + false); + + // trigger a connect event + Connect(); + + ASSERT_TRUE(nt::WaitForConnectionListenerQueue(server_inst, 1.0)); + + // get the event + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(handle, result[0].listener); + EXPECT_TRUE(result[0].connected); + result.clear(); + + // trigger a disconnect event + nt::StopClient(client_inst); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + // get the event + ASSERT_EQ(result.size(), 1u); + EXPECT_EQ(handle, result[0].listener); + EXPECT_FALSE(result[0].connected); +} diff --git a/src/test/native/cpp/EntryListenerTest.cpp b/src/test/native/cpp/EntryListenerTest.cpp new file mode 100644 index 0000000..0dbd691 --- /dev/null +++ b/src/test/native/cpp/EntryListenerTest.cpp @@ -0,0 +1,165 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "ntcore_cpp.h" + +#include +#include + +#include "TestPrinters.h" +#include "ValueMatcher.h" +#include "gtest/gtest.h" + +class EntryListenerTest : public ::testing::Test { + public: + EntryListenerTest() + : server_inst(nt::CreateInstance()), client_inst(nt::CreateInstance()) { + nt::SetNetworkIdentity(server_inst, "server"); + nt::SetNetworkIdentity(client_inst, "client"); +#if 0 + nt::AddLogger(server_inst, + [](const nt::LogMessage& msg) { + std::fprintf(stderr, "SERVER: %s\n", msg.message.c_str()); + }, + 0, UINT_MAX); + nt::AddLogger(client_inst, + [](const nt::LogMessage& msg) { + std::fprintf(stderr, "CLIENT: %s\n", msg.message.c_str()); + }, + 0, UINT_MAX); +#endif + } + + ~EntryListenerTest() override { + nt::DestroyInstance(server_inst); + nt::DestroyInstance(client_inst); + } + + void Connect(); + + protected: + NT_Inst server_inst; + NT_Inst client_inst; +}; + +void EntryListenerTest::Connect() { + nt::StartServer(server_inst, "entrylistenertest.ini", "127.0.0.1", 10000); + nt::StartClient(client_inst, "127.0.0.1", 10000); + + // Use connection listener to ensure we've connected + NT_ConnectionListenerPoller poller = + nt::CreateConnectionListenerPoller(server_inst); + nt::AddPolledConnectionListener(poller, false); + bool timed_out = false; + if (nt::PollConnectionListener(poller, 1.0, &timed_out).empty()) { + FAIL() << "client didn't connect to server"; + } +} + +TEST_F(EntryListenerTest, EntryNewLocal) { + std::vector events; + auto handle = nt::AddEntryListener( + nt::GetEntry(server_inst, "/foo"), + [&](const nt::EntryNotification& event) { events.push_back(event); }, + NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); + + // Trigger an event + nt::SetEntryValue(nt::GetEntry(server_inst, "/foo/bar"), + nt::Value::MakeDouble(2.0)); + nt::SetEntryValue(nt::GetEntry(server_inst, "/foo"), + nt::Value::MakeDouble(1.0)); + + ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0)); + + // Check the event + ASSERT_EQ(events.size(), 1u); + ASSERT_EQ(events[0].listener, handle); + ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo")); + ASSERT_EQ(events[0].name, "/foo"); + ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0))); + ASSERT_EQ(events[0].flags, (unsigned int)(NT_NOTIFY_NEW | NT_NOTIFY_LOCAL)); +} + +TEST_F(EntryListenerTest, EntryNewRemote) { + Connect(); + if (HasFatalFailure()) return; + std::vector events; + auto handle = nt::AddEntryListener( + nt::GetEntry(server_inst, "/foo"), + [&](const nt::EntryNotification& event) { events.push_back(event); }, + NT_NOTIFY_NEW); + + // Trigger an event + nt::SetEntryValue(nt::GetEntry(client_inst, "/foo/bar"), + nt::Value::MakeDouble(2.0)); + nt::SetEntryValue(nt::GetEntry(client_inst, "/foo"), + nt::Value::MakeDouble(1.0)); + nt::Flush(client_inst); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0)); + + // Check the event + ASSERT_EQ(events.size(), 1u); + ASSERT_EQ(events[0].listener, handle); + ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo")); + ASSERT_EQ(events[0].name, "/foo"); + ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0))); + ASSERT_EQ(events[0].flags, NT_NOTIFY_NEW); +} + +TEST_F(EntryListenerTest, PrefixNewLocal) { + std::vector events; + auto handle = nt::AddEntryListener( + server_inst, "/foo", + [&](const nt::EntryNotification& event) { events.push_back(event); }, + NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); + + // Trigger an event + nt::SetEntryValue(nt::GetEntry(server_inst, "/foo/bar"), + nt::Value::MakeDouble(1.0)); + nt::SetEntryValue(nt::GetEntry(server_inst, "/baz"), + nt::Value::MakeDouble(1.0)); + + ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0)); + + // Check the event + ASSERT_EQ(events.size(), 1u); + ASSERT_EQ(events[0].listener, handle); + ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo/bar")); + ASSERT_EQ(events[0].name, "/foo/bar"); + ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0))); + ASSERT_EQ(events[0].flags, (unsigned int)(NT_NOTIFY_NEW | NT_NOTIFY_LOCAL)); +} + +TEST_F(EntryListenerTest, PrefixNewRemote) { + Connect(); + if (HasFatalFailure()) return; + std::vector events; + auto handle = nt::AddEntryListener( + server_inst, "/foo", + [&](const nt::EntryNotification& event) { events.push_back(event); }, + NT_NOTIFY_NEW); + + // Trigger an event + nt::SetEntryValue(nt::GetEntry(client_inst, "/foo/bar"), + nt::Value::MakeDouble(1.0)); + nt::SetEntryValue(nt::GetEntry(client_inst, "/baz"), + nt::Value::MakeDouble(1.0)); + nt::Flush(client_inst); + std::this_thread::sleep_for(std::chrono::milliseconds(100)); + + ASSERT_TRUE(nt::WaitForEntryListenerQueue(server_inst, 1.0)); + + // Check the event + ASSERT_EQ(events.size(), 1u); + ASSERT_EQ(events[0].listener, handle); + ASSERT_EQ(events[0].entry, nt::GetEntry(server_inst, "/foo/bar")); + ASSERT_EQ(events[0].name, "/foo/bar"); + ASSERT_THAT(events[0].value, nt::ValueEq(nt::Value::MakeDouble(1.0))); + ASSERT_EQ(events[0].flags, NT_NOTIFY_NEW); +} diff --git a/src/test/native/cpp/EntryNotifierTest.cpp b/src/test/native/cpp/EntryNotifierTest.cpp new file mode 100644 index 0000000..b5d44a4 --- /dev/null +++ b/src/test/native/cpp/EntryNotifierTest.cpp @@ -0,0 +1,316 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "EntryNotifier.h" + +#include "gtest/gtest.h" + +#include "support/Logger.h" + +#include "TestPrinters.h" +#include "ValueMatcher.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::IsNull; +using ::testing::Return; + +namespace nt { + +class EntryNotifierTest : public ::testing::Test { + public: + EntryNotifierTest() : notifier(1, logger) { notifier.Start(); } + + void GenerateNotifications(); + + protected: + wpi::Logger logger; + EntryNotifier notifier; +}; + +void EntryNotifierTest::GenerateNotifications() { + // All flags combos that can be generated by Storage + static const unsigned int flags[] = { + // "normal" notifications + NT_NOTIFY_NEW, NT_NOTIFY_DELETE, NT_NOTIFY_UPDATE, NT_NOTIFY_FLAGS, + NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS, + // immediate notifications are always "new" + NT_NOTIFY_IMMEDIATE | NT_NOTIFY_NEW, + // local notifications can be of any flag combo + NT_NOTIFY_LOCAL | NT_NOTIFY_NEW, NT_NOTIFY_LOCAL | NT_NOTIFY_DELETE, + NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE, NT_NOTIFY_LOCAL | NT_NOTIFY_FLAGS, + NT_NOTIFY_LOCAL | NT_NOTIFY_UPDATE | NT_NOTIFY_FLAGS}; + // Generate across keys + static const char* keys[] = {"/foo/bar", "/baz", "/boo"}; + + auto val = Value::MakeDouble(1); + + // Provide unique key indexes for each key + unsigned int keyindex = 5; + for (auto key : keys) { + for (auto flag : flags) { + notifier.NotifyEntry(keyindex, key, val, flag); + } + ++keyindex; + } +} + +TEST_F(EntryNotifierTest, PollEntryMultiple) { + auto poller1 = notifier.CreatePoller(); + auto poller2 = notifier.CreatePoller(); + auto poller3 = notifier.CreatePoller(); + auto h1 = notifier.AddPolled(poller1, 6, NT_NOTIFY_NEW); + auto h2 = notifier.AddPolled(poller2, 6, NT_NOTIFY_NEW); + auto h3 = notifier.AddPolled(poller3, 6, NT_NOTIFY_UPDATE); + + ASSERT_FALSE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results1 = notifier.Poll(poller1, 0, &timed_out); + ASSERT_FALSE(timed_out); + auto results2 = notifier.Poll(poller2, 0, &timed_out); + ASSERT_FALSE(timed_out); + auto results3 = notifier.Poll(poller3, 0, &timed_out); + ASSERT_FALSE(timed_out); + + ASSERT_EQ(results1.size(), 2u); + for (const auto& result : results1) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h1); + } + + ASSERT_EQ(results2.size(), 2u); + for (const auto& result : results2) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h2); + } + + ASSERT_EQ(results3.size(), 2u); + for (const auto& result : results3) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h3); + } +} + +TEST_F(EntryNotifierTest, PollEntryBasic) { + auto poller = notifier.CreatePoller(); + auto g1 = notifier.AddPolled(poller, 6, NT_NOTIFY_NEW); + auto g2 = notifier.AddPolled(poller, 6, NT_NOTIFY_DELETE); + auto g3 = notifier.AddPolled(poller, 6, NT_NOTIFY_UPDATE); + auto g4 = notifier.AddPolled(poller, 6, NT_NOTIFY_FLAGS); + + ASSERT_FALSE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results = notifier.Poll(poller, 0, &timed_out); + ASSERT_FALSE(timed_out); + + int g1count = 0; + int g2count = 0; + int g3count = 0; + int g4count = 0; + for (const auto& result : results) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_EQ(result.name, "/baz"); + EXPECT_THAT(result.value, ValueEq(Value::MakeDouble(1))); + EXPECT_EQ(Handle{result.entry}.GetType(), Handle::kEntry); + EXPECT_EQ(Handle{result.entry}.GetInst(), 1); + EXPECT_EQ(Handle{result.entry}.GetIndex(), 6); + EXPECT_EQ(Handle{result.listener}.GetType(), Handle::kEntryListener); + EXPECT_EQ(Handle{result.listener}.GetInst(), 1); + if (Handle{result.listener}.GetIndex() == (int)g1) { + ++g1count; + EXPECT_TRUE((result.flags & NT_NOTIFY_NEW) != 0); + } else if (Handle{result.listener}.GetIndex() == (int)g2) { + ++g2count; + EXPECT_TRUE((result.flags & NT_NOTIFY_DELETE) != 0); + } else if (Handle{result.listener}.GetIndex() == (int)g3) { + ++g3count; + EXPECT_TRUE((result.flags & NT_NOTIFY_UPDATE) != 0); + } else if (Handle{result.listener}.GetIndex() == (int)g4) { + ++g4count; + EXPECT_TRUE((result.flags & NT_NOTIFY_FLAGS) != 0); + } else { + ADD_FAILURE() << "unknown listener index"; + } + } + EXPECT_EQ(g1count, 2); + EXPECT_EQ(g2count, 1); // NT_NOTIFY_DELETE + EXPECT_EQ(g3count, 2); + EXPECT_EQ(g4count, 2); +} + +TEST_F(EntryNotifierTest, PollEntryImmediate) { + auto poller = notifier.CreatePoller(); + notifier.AddPolled(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE); + notifier.AddPolled(poller, 6, NT_NOTIFY_NEW); + + ASSERT_FALSE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results = notifier.Poll(poller, 0, &timed_out); + ASSERT_FALSE(timed_out); + SCOPED_TRACE(::testing::PrintToString(results)); + ASSERT_EQ(results.size(), 4u); +} + +TEST_F(EntryNotifierTest, PollEntryLocal) { + auto poller = notifier.CreatePoller(); + notifier.AddPolled(poller, 6, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); + notifier.AddPolled(poller, 6, NT_NOTIFY_NEW); + + ASSERT_TRUE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results = notifier.Poll(poller, 0, &timed_out); + ASSERT_FALSE(timed_out); + SCOPED_TRACE(::testing::PrintToString(results)); + ASSERT_EQ(results.size(), 6u); +} + +TEST_F(EntryNotifierTest, PollPrefixMultiple) { + auto poller1 = notifier.CreatePoller(); + auto poller2 = notifier.CreatePoller(); + auto poller3 = notifier.CreatePoller(); + auto h1 = notifier.AddPolled(poller1, "/foo", NT_NOTIFY_NEW); + auto h2 = notifier.AddPolled(poller2, "/foo", NT_NOTIFY_NEW); + auto h3 = notifier.AddPolled(poller3, "/foo", NT_NOTIFY_UPDATE); + + ASSERT_FALSE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results1 = notifier.Poll(poller1, 0, &timed_out); + ASSERT_FALSE(timed_out); + auto results2 = notifier.Poll(poller2, 0, &timed_out); + ASSERT_FALSE(timed_out); + auto results3 = notifier.Poll(poller3, 0, &timed_out); + ASSERT_FALSE(timed_out); + + ASSERT_EQ(results1.size(), 2u); + for (const auto& result : results1) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h1); + } + + ASSERT_EQ(results2.size(), 2u); + for (const auto& result : results2) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h2); + } + + ASSERT_EQ(results3.size(), 2u); + for (const auto& result : results3) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_EQ(Handle{result.listener}.GetIndex(), (int)h3); + } +} + +TEST_F(EntryNotifierTest, PollPrefixBasic) { + auto poller = notifier.CreatePoller(); + auto g1 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW); + auto g2 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_DELETE); + auto g3 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_UPDATE); + auto g4 = notifier.AddPolled(poller, "/foo", NT_NOTIFY_FLAGS); + notifier.AddPolled(poller, "/bar", NT_NOTIFY_NEW); + notifier.AddPolled(poller, "/bar", NT_NOTIFY_DELETE); + notifier.AddPolled(poller, "/bar", NT_NOTIFY_UPDATE); + notifier.AddPolled(poller, "/bar", NT_NOTIFY_FLAGS); + + ASSERT_FALSE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results = notifier.Poll(poller, 0, &timed_out); + ASSERT_FALSE(timed_out); + + int g1count = 0; + int g2count = 0; + int g3count = 0; + int g4count = 0; + for (const auto& result : results) { + SCOPED_TRACE(::testing::PrintToString(result)); + EXPECT_TRUE(StringRef(result.name).startswith("/foo")); + EXPECT_THAT(result.value, ValueEq(Value::MakeDouble(1))); + EXPECT_EQ(Handle{result.entry}.GetType(), Handle::kEntry); + EXPECT_EQ(Handle{result.entry}.GetInst(), 1); + EXPECT_EQ(Handle{result.entry}.GetIndex(), 5); + EXPECT_EQ(Handle{result.listener}.GetType(), Handle::kEntryListener); + EXPECT_EQ(Handle{result.listener}.GetInst(), 1); + if (Handle{result.listener}.GetIndex() == (int)g1) { + ++g1count; + EXPECT_TRUE((result.flags & NT_NOTIFY_NEW) != 0); + } else if (Handle{result.listener}.GetIndex() == (int)g2) { + ++g2count; + EXPECT_TRUE((result.flags & NT_NOTIFY_DELETE) != 0); + } else if (Handle{result.listener}.GetIndex() == (int)g3) { + ++g3count; + EXPECT_TRUE((result.flags & NT_NOTIFY_UPDATE) != 0); + } else if (Handle{result.listener}.GetIndex() == (int)g4) { + ++g4count; + EXPECT_TRUE((result.flags & NT_NOTIFY_FLAGS) != 0); + } else { + ADD_FAILURE() << "unknown listener index"; + } + } + EXPECT_EQ(g1count, 2); + EXPECT_EQ(g2count, 1); // NT_NOTIFY_DELETE + EXPECT_EQ(g3count, 2); + EXPECT_EQ(g4count, 2); +} + +TEST_F(EntryNotifierTest, PollPrefixImmediate) { + auto poller = notifier.CreatePoller(); + notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_IMMEDIATE); + notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW); + + ASSERT_FALSE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results = notifier.Poll(poller, 0, &timed_out); + ASSERT_FALSE(timed_out); + SCOPED_TRACE(::testing::PrintToString(results)); + ASSERT_EQ(results.size(), 4u); +} + +TEST_F(EntryNotifierTest, PollPrefixLocal) { + auto poller = notifier.CreatePoller(); + notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW | NT_NOTIFY_LOCAL); + notifier.AddPolled(poller, "/foo", NT_NOTIFY_NEW); + + ASSERT_TRUE(notifier.local_notifiers()); + + GenerateNotifications(); + + ASSERT_TRUE(notifier.WaitForQueue(1.0)); + bool timed_out = false; + auto results = notifier.Poll(poller, 0, &timed_out); + ASSERT_FALSE(timed_out); + SCOPED_TRACE(::testing::PrintToString(results)); + ASSERT_EQ(results.size(), 6u); +} + +} // namespace nt diff --git a/src/test/native/cpp/MessageMatcher.cpp b/src/test/native/cpp/MessageMatcher.cpp new file mode 100644 index 0000000..aabacba --- /dev/null +++ b/src/test/native/cpp/MessageMatcher.cpp @@ -0,0 +1,52 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "MessageMatcher.h" + +namespace nt { + +bool MessageMatcher::MatchAndExplain( + std::shared_ptr msg, + ::testing::MatchResultListener* listener) const { + bool match = true; + if (!msg) return false; + if (msg->str() != goodmsg->str()) { + *listener << "str mismatch "; + match = false; + } + if ((!msg->value() && goodmsg->value()) || + (msg->value() && !goodmsg->value()) || + (msg->value() && goodmsg->value() && + *msg->value() != *goodmsg->value())) { + *listener << "value mismatch "; + match = false; + } + if (msg->id() != goodmsg->id()) { + *listener << "id mismatch "; + match = false; + } + if (msg->flags() != goodmsg->flags()) { + *listener << "flags mismatch"; + match = false; + } + if (msg->seq_num_uid() != goodmsg->seq_num_uid()) { + *listener << "seq_num_uid mismatch"; + match = false; + } + return match; +} + +void MessageMatcher::DescribeTo(::std::ostream* os) const { + PrintTo(goodmsg, os); +} + +void MessageMatcher::DescribeNegationTo(::std::ostream* os) const { + *os << "is not equal to "; + PrintTo(goodmsg, os); +} + +} // namespace nt diff --git a/src/test/native/cpp/MessageMatcher.h b/src/test/native/cpp/MessageMatcher.h new file mode 100644 index 0000000..cf959e1 --- /dev/null +++ b/src/test/native/cpp/MessageMatcher.h @@ -0,0 +1,43 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TEST_MESSAGEMATCHER_H_ +#define NT_TEST_MESSAGEMATCHER_H_ + +#include +#include + +#include "gmock/gmock.h" + +#include "TestPrinters.h" + +#include "Message.h" + +namespace nt { + +class MessageMatcher + : public ::testing::MatcherInterface> { + public: + MessageMatcher(std::shared_ptr goodmsg_) : goodmsg(goodmsg_) {} + + bool MatchAndExplain(std::shared_ptr msg, + ::testing::MatchResultListener* listener) const override; + void DescribeTo(::std::ostream* os) const override; + void DescribeNegationTo(::std::ostream* os) const override; + + private: + std::shared_ptr goodmsg; +}; + +inline ::testing::Matcher> MessageEq( + std::shared_ptr goodmsg) { + return ::testing::MakeMatcher(new MessageMatcher(goodmsg)); +} + +} // namespace nt + +#endif // NT_TEST_MESSAGEMATCHER_H_ diff --git a/src/test/native/cpp/MockConnectionNotifier.h b/src/test/native/cpp/MockConnectionNotifier.h new file mode 100644 index 0000000..4a5667a --- /dev/null +++ b/src/test/native/cpp/MockConnectionNotifier.h @@ -0,0 +1,26 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TEST_MOCKCONNECTIONNOTIFIER_H_ +#define NT_TEST_MOCKCONNECTIONNOTIFIER_H_ + +#include "gmock/gmock.h" + +#include "IConnectionNotifier.h" + +namespace nt { + +class MockConnectionNotifier : public IConnectionNotifier { + public: + MOCK_METHOD3(NotifyConnection, + void(bool connected, const ConnectionInfo& conn_info, + unsigned int only_listener)); +}; + +} // namespace nt + +#endif // NT_TEST_MOCKCONNECTIONNOTIFIER_H_ diff --git a/src/test/native/cpp/MockDispatcher.h b/src/test/native/cpp/MockDispatcher.h new file mode 100644 index 0000000..7966ccb --- /dev/null +++ b/src/test/native/cpp/MockDispatcher.h @@ -0,0 +1,26 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TEST_MOCKDISPATCHER_H_ +#define NT_TEST_MOCKDISPATCHER_H_ + +#include "gmock/gmock.h" + +#include "IDispatcher.h" + +namespace nt { + +class MockDispatcher : public IDispatcher { + public: + MOCK_METHOD3(QueueOutgoing, + void(std::shared_ptr msg, NetworkConnection* only, + NetworkConnection* except)); +}; + +} // namespace nt + +#endif // NT_TEST_MOCKDISPATCHER_H_ diff --git a/src/test/native/cpp/MockEntryNotifier.h b/src/test/native/cpp/MockEntryNotifier.h new file mode 100644 index 0000000..2a31764 --- /dev/null +++ b/src/test/native/cpp/MockEntryNotifier.h @@ -0,0 +1,28 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TEST_MOCKENTRYNOTIFIER_H_ +#define NT_TEST_MOCKENTRYNOTIFIER_H_ + +#include "gmock/gmock.h" + +#include "IEntryNotifier.h" + +namespace nt { + +class MockEntryNotifier : public IEntryNotifier { + public: + MOCK_CONST_METHOD0(local_notifiers, bool()); + MOCK_METHOD5(NotifyEntry, + void(unsigned int local_id, StringRef name, + std::shared_ptr value, unsigned int flags, + unsigned int only_listener)); +}; + +} // namespace nt + +#endif // NT_TEST_MOCKENTRYNOTIFIER_H_ diff --git a/src/test/native/cpp/MockRpcServer.h b/src/test/native/cpp/MockRpcServer.h new file mode 100644 index 0000000..fcf8248 --- /dev/null +++ b/src/test/native/cpp/MockRpcServer.h @@ -0,0 +1,30 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TEST_MOCKRPCSERVER_H_ +#define NT_TEST_MOCKRPCSERVER_H_ + +#include "gmock/gmock.h" + +#include "IRpcServer.h" + +namespace nt { + +class MockRpcServer : public IRpcServer { + public: + MOCK_METHOD0(Start, void()); + MOCK_METHOD1(RemoveRpc, void(unsigned int rpc_uid)); + MOCK_METHOD7(ProcessRpc, + void(unsigned int local_id, unsigned int call_uid, + StringRef name, StringRef params, + const ConnectionInfo& conn, SendResponseFunc send_response, + unsigned int rpc_uid)); +}; + +} // namespace nt + +#endif // NT_TEST_MOCKRPCSERVER_H_ diff --git a/src/test/native/cpp/NetworkTableTest.cpp b/src/test/native/cpp/NetworkTableTest.cpp index 24ad29f..706f059 100644 --- a/src/test/native/cpp/NetworkTableTest.cpp +++ b/src/test/native/cpp/NetworkTableTest.cpp @@ -6,22 +6,39 @@ /*----------------------------------------------------------------------------*/ #include "networktables/NetworkTable.h" +#include "networktables/NetworkTableInstance.h" #include "gtest/gtest.h" +#include "TestPrinters.h" class NetworkTableTest : public ::testing::Test {}; TEST_F(NetworkTableTest, ContainsKey) { - auto nt = NetworkTable::GetTable("containskey"); + auto inst = nt::NetworkTableInstance::Create(); + auto nt = inst.GetTable("containskey"); ASSERT_FALSE(nt->ContainsKey("testkey")); nt->PutNumber("testkey", 5); ASSERT_TRUE(nt->ContainsKey("testkey")); + ASSERT_TRUE(inst.GetEntry("/containskey/testkey").Exists()); + ASSERT_FALSE(inst.GetEntry("containskey/testkey").Exists()); } TEST_F(NetworkTableTest, LeadingSlash) { - auto nt = NetworkTable::GetTable("leadingslash"); - auto nt2 = NetworkTable::GetTable("/leadingslash"); + auto inst = nt::NetworkTableInstance::Create(); + auto nt = inst.GetTable("leadingslash"); + auto nt2 = inst.GetTable("/leadingslash"); ASSERT_FALSE(nt->ContainsKey("testkey")); nt2->PutNumber("testkey", 5); ASSERT_TRUE(nt->ContainsKey("testkey")); + ASSERT_TRUE(inst.GetEntry("/leadingslash/testkey").Exists()); +} + +TEST_F(NetworkTableTest, EmptyOrNoSlash) { + auto inst = nt::NetworkTableInstance::Create(); + auto nt = inst.GetTable("/"); + auto nt2 = inst.GetTable(""); + ASSERT_FALSE(nt->ContainsKey("testkey")); + nt2->PutNumber("testkey", 5); + ASSERT_TRUE(nt->ContainsKey("testkey")); + ASSERT_TRUE(inst.GetEntry("/testkey").Exists()); } diff --git a/src/test/native/cpp/StorageTest.cpp b/src/test/native/cpp/StorageTest.cpp index 8229d96..34b00c7 100644 --- a/src/test/native/cpp/StorageTest.cpp +++ b/src/test/native/cpp/StorageTest.cpp @@ -5,44 +5,81 @@ /* the project. */ /*----------------------------------------------------------------------------*/ -#include "Storage.h" #include "StorageTest.h" +#include "Storage.h" #include #include "gtest/gtest.h" #include "gmock/gmock.h" +#include "MessageMatcher.h" +#include "TestPrinters.h" +#include "ValueMatcher.h" + +using ::testing::_; +using ::testing::AnyNumber; +using ::testing::IsNull; +using ::testing::Return; + namespace nt { class StorageTestEmpty : public StorageTest, public ::testing::TestWithParam { public: - StorageTestEmpty() { HookOutgoing(GetParam()); } + StorageTestEmpty() { + HookOutgoing(GetParam()); + EXPECT_CALL(notifier, local_notifiers()) + .Times(AnyNumber()) + .WillRepeatedly(Return(true)); + } }; class StorageTestPopulateOne : public StorageTestEmpty { public: StorageTestPopulateOne() { + EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber()); + EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(notifier, local_notifiers()) + .Times(AnyNumber()) + .WillRepeatedly(Return(false)); storage.SetEntryTypeValue("foo", Value::MakeBoolean(true)); - outgoing.clear(); + ::testing::Mock::VerifyAndClearExpectations(&dispatcher); + ::testing::Mock::VerifyAndClearExpectations(¬ifier); + EXPECT_CALL(notifier, local_notifiers()) + .Times(AnyNumber()) + .WillRepeatedly(Return(true)); } }; class StorageTestPopulated : public StorageTestEmpty { public: StorageTestPopulated() { + EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber()); + EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(notifier, local_notifiers()) + .Times(AnyNumber()) + .WillRepeatedly(Return(false)); storage.SetEntryTypeValue("foo", Value::MakeBoolean(true)); storage.SetEntryTypeValue("foo2", Value::MakeDouble(0.0)); storage.SetEntryTypeValue("bar", Value::MakeDouble(1.0)); storage.SetEntryTypeValue("bar2", Value::MakeBoolean(false)); - outgoing.clear(); + ::testing::Mock::VerifyAndClearExpectations(&dispatcher); + ::testing::Mock::VerifyAndClearExpectations(¬ifier); + EXPECT_CALL(notifier, local_notifiers()) + .Times(AnyNumber()) + .WillRepeatedly(Return(true)); } }; class StorageTestPersistent : public StorageTestEmpty { public: StorageTestPersistent() { + EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber()); + EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)).Times(AnyNumber()); + EXPECT_CALL(notifier, local_notifiers()) + .Times(AnyNumber()) + .WillRepeatedly(Return(false)); storage.SetEntryTypeValue("boolean/true", Value::MakeBoolean(true)); storage.SetEntryTypeValue("boolean/false", Value::MakeBoolean(false)); storage.SetEntryTypeValue("double/neg", Value::MakeDouble(-1.5)); @@ -80,7 +117,11 @@ class StorageTestPersistent : public StorageTestEmpty { storage.SetEntryTypeValue(StringRef("\0\3\5\n", 4), Value::MakeBoolean(true)); storage.SetEntryTypeValue("=", Value::MakeBoolean(true)); - outgoing.clear(); + ::testing::Mock::VerifyAndClearExpectations(&dispatcher); + ::testing::Mock::VerifyAndClearExpectations(¬ifier); + EXPECT_CALL(notifier, local_notifiers()) + .Times(AnyNumber()) + .WillRepeatedly(Return(true)); } }; @@ -107,19 +148,27 @@ TEST_P(StorageTestEmpty, GetEntryValueNotExist) { EXPECT_FALSE(storage.GetEntryValue("foo")); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, GetEntryValueExist) { auto value = Value::MakeBoolean(true); + EXPECT_CALL(dispatcher, QueueOutgoing(_, IsNull(), IsNull())); + EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)); storage.SetEntryTypeValue("foo", value); - outgoing.clear(); EXPECT_EQ(value, storage.GetEntryValue("foo")); } TEST_P(StorageTestEmpty, SetEntryTypeValueAssignNew) { // brand new entry auto value = Value::MakeBoolean(true); + // id assigned if server + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryAssign( + "foo", GetParam() ? 0 : 0xffff, 1, value, 0)), + IsNull(), IsNull())); + EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), value, + NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX)); + storage.SetEntryTypeValue("foo", value); EXPECT_EQ(value, GetEntry("foo")->value); if (GetParam()) { @@ -128,41 +177,23 @@ TEST_P(StorageTestEmpty, SetEntryTypeValueAssignNew) { } else { EXPECT_TRUE(idmap().empty()); } - - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryAssign, msg->type()); - EXPECT_EQ("foo", msg->str()); - if (GetParam()) - EXPECT_EQ(0u, msg->id()); // assigned as server - else - EXPECT_EQ(0xffffu, msg->id()); // not assigned as client - EXPECT_EQ(1u, msg->seq_num_uid()); - EXPECT_EQ(value, msg->value()); - EXPECT_EQ(0u, msg->flags()); } TEST_P(StorageTestPopulateOne, SetEntryTypeValueAssignTypeChange) { // update with different type results in assignment message auto value = Value::MakeDouble(0.0); + + // id assigned if server; seq_num incremented + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryAssign( + "foo", GetParam() ? 0 : 0xffff, 2, value, 0)), + IsNull(), IsNull())); + EXPECT_CALL(notifier, + NotifyEntry(0, StringRef("foo"), value, + NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX)); + storage.SetEntryTypeValue("foo", value); EXPECT_EQ(value, GetEntry("foo")->value); - - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryAssign, msg->type()); - EXPECT_EQ("foo", msg->str()); - if (GetParam()) - EXPECT_EQ(0u, msg->id()); // assigned as server - else - EXPECT_EQ(0xffffu, msg->id()); // not assigned as client - EXPECT_EQ(2u, msg->seq_num_uid()); // incremented - EXPECT_EQ(value, msg->value()); - EXPECT_EQ(0u, msg->flags()); } TEST_P(StorageTestPopulateOne, SetEntryTypeValueEqualValue) { @@ -171,28 +202,28 @@ TEST_P(StorageTestPopulateOne, SetEntryTypeValueEqualValue) { auto value = Value::MakeBoolean(true); storage.SetEntryTypeValue("foo", value); EXPECT_EQ(value, GetEntry("foo")->value); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulated, SetEntryTypeValueDifferentValue) { // update with same type and different value results in value update message auto value = Value::MakeDouble(1.0); + + // client shouldn't send an update as id not assigned yet + if (GetParam()) { + // id assigned if server; seq_num incremented + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)), + IsNull(), IsNull())); + } + EXPECT_CALL(notifier, + NotifyEntry(1, StringRef("foo2"), value, + NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX)); storage.SetEntryTypeValue("foo2", value); EXPECT_EQ(value, GetEntry("foo2")->value); - if (GetParam()) { - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryUpdate, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - EXPECT_EQ(2u, msg->seq_num_uid()); // incremented - EXPECT_EQ(value, msg->value()); - } else { - // shouldn't send an update id not assigned yet (happens on client only) - EXPECT_TRUE(outgoing.empty()); - EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); // still should be incremented + if (!GetParam()) { + // seq_num should still be incremented + EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); } } @@ -201,44 +232,36 @@ TEST_P(StorageTestEmpty, SetEntryTypeValueEmptyName) { storage.SetEntryTypeValue("", value); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, SetEntryTypeValueEmptyValue) { storage.SetEntryTypeValue("foo", nullptr); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, SetEntryValueAssignNew) { // brand new entry auto value = Value::MakeBoolean(true); + + // id assigned if server + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryAssign( + "foo", GetParam() ? 0 : 0xffff, 1, value, 0)), + IsNull(), IsNull())); + EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), value, + NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX)); + EXPECT_TRUE(storage.SetEntryValue("foo", value)); EXPECT_EQ(value, GetEntry("foo")->value); - - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryAssign, msg->type()); - EXPECT_EQ("foo", msg->str()); - if (GetParam()) - EXPECT_EQ(0u, msg->id()); // assigned as server - else - EXPECT_EQ(0xffffu, msg->id()); // not assigned as client - EXPECT_EQ(0u, msg->seq_num_uid()); - EXPECT_EQ(value, msg->value()); - EXPECT_EQ(0u, msg->flags()); } TEST_P(StorageTestPopulateOne, SetEntryValueAssignTypeChange) { - // update with different type results in error and no message + // update with different type results in error and no message or notification auto value = Value::MakeDouble(0.0); EXPECT_FALSE(storage.SetEntryValue("foo", value)); auto entry = GetEntry("foo"); EXPECT_NE(value, entry->value); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulateOne, SetEntryValueEqualValue) { @@ -248,29 +271,30 @@ TEST_P(StorageTestPopulateOne, SetEntryValueEqualValue) { EXPECT_TRUE(storage.SetEntryValue("foo", value)); auto entry = GetEntry("foo"); EXPECT_EQ(value, entry->value); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulated, SetEntryValueDifferentValue) { // update with same type and different value results in value update message auto value = Value::MakeDouble(1.0); + + // client shouldn't send an update as id not assigned yet + if (GetParam()) { + // id assigned if server; seq_num incremented + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)), + IsNull(), IsNull())); + } + EXPECT_CALL(notifier, + NotifyEntry(1, StringRef("foo2"), value, + NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX)); + EXPECT_TRUE(storage.SetEntryValue("foo2", value)); auto entry = GetEntry("foo2"); EXPECT_EQ(value, entry->value); - if (GetParam()) { - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryUpdate, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - EXPECT_EQ(2u, msg->seq_num_uid()); // incremented - EXPECT_EQ(value, msg->value()); - } else { - // shouldn't send an update id not assigned yet (happens on client only) - EXPECT_TRUE(outgoing.empty()); - EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); // still should be incremented + if (!GetParam()) { + // seq_num should still be incremented + EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); } } @@ -279,36 +303,29 @@ TEST_P(StorageTestEmpty, SetEntryValueEmptyName) { EXPECT_TRUE(storage.SetEntryValue("", value)); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, SetEntryValueEmptyValue) { EXPECT_TRUE(storage.SetEntryValue("foo", nullptr)); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, SetDefaultEntryAssignNew) { // brand new entry auto value = Value::MakeBoolean(true); + + // id assigned if server + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryAssign( + "foo", GetParam() ? 0 : 0xffff, 1, value, 0)), + IsNull(), IsNull())); + EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), value, + NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX)); + auto ret_val = storage.SetDefaultEntryValue("foo", value); EXPECT_TRUE(ret_val); EXPECT_EQ(value, GetEntry("foo")->value); - - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryAssign, msg->type()); - EXPECT_EQ("foo", msg->str()); - if (GetParam()) - EXPECT_EQ(0u, msg->id()); // assigned as server - else - EXPECT_EQ(0xffffu, msg->id()); // not assigned as client - EXPECT_EQ(0u, msg->seq_num_uid()); - EXPECT_EQ(value, msg->value()); - EXPECT_EQ(0u, msg->flags()); } TEST_P(StorageTestPopulateOne, SetDefaultEntryExistsSameType) { @@ -317,8 +334,6 @@ TEST_P(StorageTestPopulateOne, SetDefaultEntryExistsSameType) { auto ret_val = storage.SetDefaultEntryValue("foo", value); EXPECT_TRUE(ret_val); EXPECT_NE(value, GetEntry("foo")->value); - - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulateOne, SetDefaultEntryExistsDifferentType) { @@ -328,8 +343,6 @@ TEST_P(StorageTestPopulateOne, SetDefaultEntryExistsDifferentType) { EXPECT_FALSE(ret_val); // should not have updated value in table if it already existed. EXPECT_NE(value, GetEntry("foo")->value); - - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, SetDefaultEntryEmptyName) { @@ -344,7 +357,6 @@ TEST_P(StorageTestEmpty, SetDefaultEntryEmptyName) { EXPECT_EQ(SequenceNumber(), entry->seq_num); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, SetDefaultEntryEmptyValue) { @@ -359,7 +371,6 @@ TEST_P(StorageTestEmpty, SetDefaultEntryEmptyValue) { EXPECT_EQ(SequenceNumber(), entry->seq_num); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulated, SetDefaultEntryEmptyName) { @@ -372,7 +383,6 @@ TEST_P(StorageTestPopulated, SetDefaultEntryEmptyName) { EXPECT_EQ(4u, idmap().size()); else EXPECT_EQ(0u, idmap().size()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulated, SetDefaultEntryEmptyValue) { @@ -385,16 +395,13 @@ TEST_P(StorageTestPopulated, SetDefaultEntryEmptyValue) { EXPECT_EQ(4u, idmap().size()); else EXPECT_EQ(0u, idmap().size()); - EXPECT_TRUE(outgoing.empty()); } - TEST_P(StorageTestEmpty, SetEntryFlagsNew) { // flags setting doesn't create an entry storage.SetEntryFlags("foo", 0u); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulateOne, SetEntryFlagsEqualValue) { @@ -403,111 +410,101 @@ TEST_P(StorageTestPopulateOne, SetEntryFlagsEqualValue) { storage.SetEntryFlags("foo", 0u); auto entry = GetEntry("foo"); EXPECT_EQ(0u, entry->flags); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulated, SetEntryFlagsDifferentValue) { // update with different value results in flags update message - storage.SetEntryFlags("foo2", 1u); - EXPECT_EQ(1u, GetEntry("foo2")->flags); - + // client shouldn't send an update as id not assigned yet if (GetParam()) { - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kFlagsUpdate, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - EXPECT_EQ(1u, msg->flags()); - } else { - // shouldn't send an update id not assigned yet (happens on client only) - EXPECT_TRUE(outgoing.empty()); + // id assigned as this is the server + EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::FlagsUpdate(1, 1)), + IsNull(), IsNull())); } + EXPECT_CALL(notifier, + NotifyEntry(1, StringRef("foo2"), _, + NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL, UINT_MAX)); + storage.SetEntryFlags("foo2", 1u); + EXPECT_EQ(1u, GetEntry("foo2")->flags); } TEST_P(StorageTestEmpty, SetEntryFlagsEmptyName) { storage.SetEntryFlags("", 0u); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, GetEntryFlagsNotExist) { EXPECT_EQ(0u, storage.GetEntryFlags("foo")); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestPopulateOne, GetEntryFlagsExist) { + EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(AnyNumber()); + EXPECT_CALL(notifier, NotifyEntry(_, _, _, _, _)); storage.SetEntryFlags("foo", 1u); - outgoing.clear(); + ::testing::Mock::VerifyAndClearExpectations(&dispatcher); EXPECT_EQ(1u, storage.GetEntryFlags("foo")); - EXPECT_TRUE(outgoing.empty()); } -TEST_P(StorageTestEmpty, DeleteEntryNotExist) { - storage.DeleteEntry("foo"); - EXPECT_TRUE(outgoing.empty()); -} +TEST_P(StorageTestEmpty, DeleteEntryNotExist) { storage.DeleteEntry("foo"); } TEST_P(StorageTestPopulated, DeleteEntryExist) { + // client shouldn't send an update as id not assigned yet + if (GetParam()) { + // id assigned as this is the server + EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::EntryDelete(1)), + IsNull(), IsNull())); + } + EXPECT_CALL(notifier, + NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(0)), + NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL, UINT_MAX)); + storage.DeleteEntry("foo2"); EXPECT_TRUE(entries().count("foo2") == 0); if (GetParam()) { ASSERT_TRUE(idmap().size() >= 2); EXPECT_FALSE(idmap()[1]); } - - if (GetParam()) { - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryDelete, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - } else { - // shouldn't send an update id not assigned yet (happens on client only) - EXPECT_TRUE(outgoing.empty()); - } } TEST_P(StorageTestEmpty, DeleteAllEntriesEmpty) { storage.DeleteAllEntries(); - EXPECT_TRUE(outgoing.empty()); + ASSERT_TRUE(entries().empty()); } TEST_P(StorageTestPopulated, DeleteAllEntries) { + EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::ClearEntries()), + IsNull(), IsNull())); + EXPECT_CALL(notifier, NotifyEntry(_, _, _, NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL, + UINT_MAX)) + .Times(4); + storage.DeleteAllEntries(); ASSERT_TRUE(entries().empty()); - - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kClearEntries, msg->type()); } TEST_P(StorageTestPopulated, DeleteAllEntriesPersistent) { GetEntry("foo2")->flags = NT_PERSISTENT; + + EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::ClearEntries()), + IsNull(), IsNull())); + EXPECT_CALL(notifier, NotifyEntry(_, _, _, NT_NOTIFY_DELETE | NT_NOTIFY_LOCAL, + UINT_MAX)) + .Times(3); + storage.DeleteAllEntries(); ASSERT_EQ(1u, entries().size()); EXPECT_EQ(1u, entries().count("foo2")); - - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kClearEntries, msg->type()); } TEST_P(StorageTestPopulated, GetEntryInfoAll) { - auto info = storage.GetEntryInfo("", 0u); + auto info = storage.GetEntryInfo(0, "", 0u); ASSERT_EQ(4u, info.size()); } TEST_P(StorageTestPopulated, GetEntryInfoPrefix) { - auto info = storage.GetEntryInfo("foo", 0u); + auto info = storage.GetEntryInfo(0, "foo", 0u); ASSERT_EQ(2u, info.size()); if (info[0].name == "foo") { EXPECT_EQ("foo", info[0].name); @@ -523,7 +520,7 @@ TEST_P(StorageTestPopulated, GetEntryInfoPrefix) { } TEST_P(StorageTestPopulated, GetEntryInfoTypes) { - auto info = storage.GetEntryInfo("", NT_DOUBLE); + auto info = storage.GetEntryInfo(0, "", NT_DOUBLE); ASSERT_EQ(2u, info.size()); EXPECT_EQ(NT_DOUBLE, info[0].type); EXPECT_EQ(NT_DOUBLE, info[1].type); @@ -537,7 +534,7 @@ TEST_P(StorageTestPopulated, GetEntryInfoTypes) { } TEST_P(StorageTestPopulated, GetEntryInfoPrefixTypes) { - auto info = storage.GetEntryInfo("bar", NT_BOOLEAN); + auto info = storage.GetEntryInfo(0, "bar", NT_BOOLEAN); ASSERT_EQ(1u, info.size()); EXPECT_EQ("bar2", info[0].name); EXPECT_EQ(NT_BOOLEAN, info[0].type); @@ -612,78 +609,92 @@ TEST_P(StorageTestPersistent, SavePersistent) { TEST_P(StorageTestEmpty, LoadPersistentBadHeader) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; std::istringstream iss(""); - EXPECT_CALL(warn, Warn(1, llvm::StringRef("header line mismatch, ignoring rest of file"))); + EXPECT_CALL( + warn, + Warn(1, llvm::StringRef("header line mismatch, ignoring rest of file"))); EXPECT_FALSE(storage.LoadPersistent(iss, warn_func)); std::istringstream iss2("[NetworkTables"); - EXPECT_CALL(warn, Warn(1, llvm::StringRef("header line mismatch, ignoring rest of file"))); + EXPECT_CALL( + warn, + Warn(1, llvm::StringRef("header line mismatch, ignoring rest of file"))); EXPECT_FALSE(storage.LoadPersistent(iss2, warn_func)); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, LoadPersistentCommentHeader) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; std::istringstream iss( "\n; comment\n# comment\n[NetworkTables Storage 3.0]\n"); EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, LoadPersistentEmptyName) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; std::istringstream iss( "[NetworkTables Storage 3.0]\nboolean \"\"=true\n"); EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); } TEST_P(StorageTestEmpty, LoadPersistentAssign) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; + + auto value = Value::MakeBoolean(true); + + // id assigned if server + EXPECT_CALL(dispatcher, QueueOutgoing(MessageEq(Message::EntryAssign( + "foo", GetParam() ? 0 : 0xffff, 1, + value, NT_PERSISTENT)), + IsNull(), IsNull())); + EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), + ValueEq(Value::MakeBoolean(true)), + NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX)); std::istringstream iss( "[NetworkTables Storage 3.0]\nboolean \"foo\"=true\n"); EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); auto entry = GetEntry("foo"); - EXPECT_EQ(*Value::MakeBoolean(true), *entry->value); + EXPECT_EQ(*value, *entry->value); EXPECT_EQ(NT_PERSISTENT, entry->flags); - - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryAssign, msg->type()); - EXPECT_EQ("foo", msg->str()); - if (GetParam()) - EXPECT_EQ(0u, msg->id()); // assigned as server - else - EXPECT_EQ(0xffffu, msg->id()); // not assigned as client - EXPECT_EQ(1u, msg->seq_num_uid()); - EXPECT_EQ(*Value::MakeBoolean(true), *msg->value()); - EXPECT_EQ(NT_PERSISTENT, msg->flags()); } TEST_P(StorageTestPopulated, LoadPersistentUpdateFlags) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; + + // client shouldn't send an update as id not assigned yet + if (GetParam()) { + // id assigned as this is server + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::FlagsUpdate(1, NT_PERSISTENT)), + IsNull(), IsNull())); + } + EXPECT_CALL(notifier, + NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(0)), + NT_NOTIFY_FLAGS | NT_NOTIFY_LOCAL, UINT_MAX)); std::istringstream iss( "[NetworkTables Storage 3.0]\ndouble \"foo2\"=0.0\n"); @@ -691,90 +702,83 @@ TEST_P(StorageTestPopulated, LoadPersistentUpdateFlags) { auto entry = GetEntry("foo2"); EXPECT_EQ(*Value::MakeDouble(0.0), *entry->value); EXPECT_EQ(NT_PERSISTENT, entry->flags); - - if (GetParam()) { - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kFlagsUpdate, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - EXPECT_EQ(NT_PERSISTENT, msg->flags()); - } else { - // shouldn't send an update id not assigned yet (happens on client only) - EXPECT_TRUE(outgoing.empty()); - } } TEST_P(StorageTestPopulated, LoadPersistentUpdateValue) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; GetEntry("foo2")->flags = NT_PERSISTENT; + auto value = Value::MakeDouble(1.0); + + // client shouldn't send an update as id not assigned yet + if (GetParam()) { + // id assigned as this is the server; seq_num incremented + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)), + IsNull(), IsNull())); + } + EXPECT_CALL(notifier, + NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(1)), + NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, UINT_MAX)); + std::istringstream iss( "[NetworkTables Storage 3.0]\ndouble \"foo2\"=1.0\n"); EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); auto entry = GetEntry("foo2"); - EXPECT_EQ(*Value::MakeDouble(1.0), *entry->value); + EXPECT_EQ(*value, *entry->value); EXPECT_EQ(NT_PERSISTENT, entry->flags); - if (GetParam()) { - ASSERT_EQ(1u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryUpdate, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - EXPECT_EQ(2u, msg->seq_num_uid()); // incremented - EXPECT_EQ(*Value::MakeDouble(1.0), *msg->value()); - } else { - // shouldn't send an update id not assigned yet (happens on client only) - EXPECT_TRUE(outgoing.empty()); - EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); // still should be incremented + if (!GetParam()) { + // seq_num should still be incremented + EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); } } TEST_P(StorageTestPopulated, LoadPersistentUpdateValueFlags) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; + + auto value = Value::MakeDouble(1.0); + + // client shouldn't send an update as id not assigned yet + if (GetParam()) { + // id assigned as this is the server; seq_num incremented + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::EntryUpdate(1, 2, value)), + IsNull(), IsNull())); + EXPECT_CALL(dispatcher, + QueueOutgoing(MessageEq(Message::FlagsUpdate(1, NT_PERSISTENT)), + IsNull(), IsNull())); + } + EXPECT_CALL(notifier, + NotifyEntry(1, StringRef("foo2"), ValueEq(Value::MakeDouble(1)), + NT_NOTIFY_FLAGS | NT_NOTIFY_UPDATE | NT_NOTIFY_LOCAL, + UINT_MAX)); std::istringstream iss( "[NetworkTables Storage 3.0]\ndouble \"foo2\"=1.0\n"); EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); auto entry = GetEntry("foo2"); - EXPECT_EQ(*Value::MakeDouble(1.0), *entry->value); + EXPECT_EQ(*value, *entry->value); EXPECT_EQ(NT_PERSISTENT, entry->flags); - if (GetParam()) { - ASSERT_EQ(2u, outgoing.size()); - EXPECT_FALSE(outgoing[0].only); - EXPECT_FALSE(outgoing[0].except); - auto msg = outgoing[0].msg; - EXPECT_EQ(Message::kEntryUpdate, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - EXPECT_EQ(2u, msg->seq_num_uid()); // incremented - EXPECT_EQ(*Value::MakeDouble(1.0), *msg->value()); - - EXPECT_FALSE(outgoing[1].only); - EXPECT_FALSE(outgoing[1].except); - msg = outgoing[1].msg; - EXPECT_EQ(Message::kFlagsUpdate, msg->type()); - EXPECT_EQ(1u, msg->id()); // assigned as server - EXPECT_EQ(NT_PERSISTENT, msg->flags()); - } else { - // shouldn't send an update id not assigned yet (happens on client only) - EXPECT_TRUE(outgoing.empty()); - EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); // still should be incremented + if (!GetParam()) { + // seq_num should still be incremented + EXPECT_EQ(2u, GetEntry("foo2")->seq_num.value()); } } TEST_P(StorageTestEmpty, LoadPersistent) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; std::string in = "[NetworkTables Storage 3.0]\n"; in += "boolean \"\\x00\\x03\\x05\\n\"=true\n"; @@ -800,10 +804,14 @@ TEST_P(StorageTestEmpty, LoadPersistent) { in += "array string \"stringarr/one\"=\"hello\"\n"; in += "array string \"stringarr/two\"=\"hello\",\"world\\n\"\n"; + EXPECT_CALL(dispatcher, QueueOutgoing(_, _, _)).Times(22); + EXPECT_CALL(notifier, + NotifyEntry(_, _, _, NT_NOTIFY_NEW | NT_NOTIFY_LOCAL, UINT_MAX)) + .Times(22); + std::istringstream iss(in); EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); ASSERT_EQ(22u, entries().size()); - EXPECT_EQ(22u, outgoing.size()); EXPECT_EQ(*Value::MakeBoolean(true), *storage.GetEntryValue("boolean/true")); EXPECT_EQ(*Value::MakeBoolean(false), @@ -816,10 +824,8 @@ TEST_P(StorageTestEmpty, LoadPersistent) { *storage.GetEntryValue("string/normal")); EXPECT_EQ(*Value::MakeString(StringRef("\0\3\5\n", 4)), *storage.GetEntryValue("string/special")); - EXPECT_EQ(*Value::MakeRaw(""), - *storage.GetEntryValue("raw/empty")); - EXPECT_EQ(*Value::MakeRaw("hello"), - *storage.GetEntryValue("raw/normal")); + EXPECT_EQ(*Value::MakeRaw(""), *storage.GetEntryValue("raw/empty")); + EXPECT_EQ(*Value::MakeRaw("hello"), *storage.GetEntryValue("raw/normal")); EXPECT_EQ(*Value::MakeRaw(StringRef("\0\3\5\n", 4)), *storage.GetEntryValue("raw/special")); EXPECT_EQ(*Value::MakeBooleanArray(std::vector{}), @@ -848,18 +854,37 @@ TEST_P(StorageTestEmpty, LoadPersistent) { TEST_P(StorageTestEmpty, LoadPersistentWarn) { MockLoadWarn warn; - auto warn_func = - [&](std::size_t line, const char* msg) { warn.Warn(line, msg); }; + auto warn_func = [&](std::size_t line, const char* msg) { + warn.Warn(line, msg); + }; std::istringstream iss( "[NetworkTables Storage 3.0]\nboolean \"foo\"=foo\n"); - EXPECT_CALL(warn, - Warn(2, llvm::StringRef("unrecognized boolean value, not 'true' or 'false'"))); + EXPECT_CALL( + warn, Warn(2, llvm::StringRef( + "unrecognized boolean value, not 'true' or 'false'"))); EXPECT_TRUE(storage.LoadPersistent(iss, warn_func)); EXPECT_TRUE(entries().empty()); EXPECT_TRUE(idmap().empty()); - EXPECT_TRUE(outgoing.empty()); +} + +TEST_P(StorageTestEmpty, ProcessIncomingEntryAssign) { + std::shared_ptr conn; + auto value = Value::MakeDouble(1.0); + if (GetParam()) { + // id assign message reply generated on the server + EXPECT_CALL( + dispatcher, + QueueOutgoing(MessageEq(Message::EntryAssign("foo", 0, 0, value, 0)), + IsNull(), IsNull())); + } + EXPECT_CALL(notifier, NotifyEntry(0, StringRef("foo"), ValueEq(value), + NT_NOTIFY_NEW, UINT_MAX)); + + storage.ProcessIncoming( + Message::EntryAssign("foo", GetParam() ? 0xffff : 0, 0, value, 0), + conn.get(), conn); } INSTANTIATE_TEST_CASE_P(StorageTestsEmpty, StorageTestEmpty, diff --git a/src/test/native/cpp/StorageTest.h b/src/test/native/cpp/StorageTest.h index dbd91c8..6dfc3ab 100644 --- a/src/test/native/cpp/StorageTest.h +++ b/src/test/native/cpp/StorageTest.h @@ -12,42 +12,37 @@ #include #include +#include "Log.h" #include "Storage.h" +#include "MockDispatcher.h" +#include "MockEntryNotifier.h" +#include "MockRpcServer.h" + namespace nt { class StorageTest { public: - StorageTest() : tmp_entry("foobar") {} + StorageTest() : storage(notifier, rpc_server, logger), tmp_entry("foobar") {} Storage::EntriesMap& entries() { return storage.m_entries; } Storage::IdMap& idmap() { return storage.m_idmap; } Storage::Entry* GetEntry(StringRef name) { auto i = storage.m_entries.find(name); - return i == storage.m_entries.end() ? &tmp_entry : i->getValue().get(); + return i == storage.m_entries.end() ? &tmp_entry : i->getValue(); } void HookOutgoing(bool server) { - using namespace std::placeholders; - storage.SetOutgoing( - std::bind(&StorageTest::QueueOutgoing, this, _1, _2, _3), server); - } - - struct OutgoingData { - std::shared_ptr msg; - NetworkConnection* only; - NetworkConnection* except; - }; - - void QueueOutgoing(std::shared_ptr msg, NetworkConnection* only, - NetworkConnection* except) { - outgoing.emplace_back(OutgoingData{msg, only, except}); + storage.SetDispatcher(&dispatcher, server); } + wpi::Logger logger; + ::testing::StrictMock notifier; + ::testing::StrictMock rpc_server; + ::testing::StrictMock dispatcher; Storage storage; Storage::Entry tmp_entry; - std::vector outgoing; }; } // namespace nt diff --git a/src/test/native/cpp/TestPrinters.cpp b/src/test/native/cpp/TestPrinters.cpp new file mode 100644 index 0000000..a98477c --- /dev/null +++ b/src/test/native/cpp/TestPrinters.cpp @@ -0,0 +1,157 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "TestPrinters.h" + +#include "Handle.h" +#include "Message.h" +#include "networktables/NetworkTableValue.h" +#include "ntcore_cpp.h" + +namespace nt { + +void PrintTo(const EntryNotification& event, std::ostream* os) { + *os << "EntryNotification{listener="; + PrintTo(Handle{event.listener}, os); + *os << ", entry="; + PrintTo(Handle{event.entry}, os); + *os << ", name=\"" << event.name << "\", flags=" << event.flags << ", value="; + PrintTo(event.value, os); + *os << '}'; +} + +void PrintTo(const Handle& handle, std::ostream* os) { + *os << "Handle{"; + switch (handle.GetType()) { + case Handle::kConnectionListener: + *os << "kConnectionListener"; + break; + case Handle::kConnectionListenerPoller: + *os << "kConnectionListenerPoller"; + break; + case Handle::kEntry: + *os << "kEntry"; + break; + case Handle::kEntryListener: + *os << "kEntryListener"; + break; + case Handle::kEntryListenerPoller: + *os << "kEntryListenerPoller"; + break; + case Handle::kInstance: + *os << "kInstance"; + break; + case Handle::kLogger: + *os << "kLogger"; + break; + case Handle::kLoggerPoller: + *os << "kLoggerPoller"; + break; + case Handle::kRpcCall: + *os << "kRpcCall"; + break; + case Handle::kRpcCallPoller: + *os << "kRpcCallPoller"; + break; + default: + *os << "UNKNOWN"; + break; + } + *os << ", " << handle.GetInst() << ", " << handle.GetIndex() << '}'; +} + +void PrintTo(const Message& msg, std::ostream* os) { + *os << "Message{"; + switch (msg.type()) { + case Message::kKeepAlive: + *os << "kKeepAlive"; + break; + case Message::kClientHello: + *os << "kClientHello"; + break; + case Message::kProtoUnsup: + *os << "kProtoUnsup"; + break; + case Message::kServerHelloDone: + *os << "kServerHelloDone"; + break; + case Message::kServerHello: + *os << "kServerHello"; + break; + case Message::kClientHelloDone: + *os << "kClientHelloDone"; + break; + case Message::kEntryAssign: + *os << "kEntryAssign"; + break; + case Message::kEntryUpdate: + *os << "kEntryUpdate"; + break; + case Message::kFlagsUpdate: + *os << "kFlagsUpdate"; + break; + case Message::kEntryDelete: + *os << "kEntryDelete"; + break; + case Message::kClearEntries: + *os << "kClearEntries"; + break; + case Message::kExecuteRpc: + *os << "kExecuteRpc"; + break; + case Message::kRpcResponse: + *os << "kRpcResponse"; + break; + default: + *os << "UNKNOWN"; + break; + } + *os << ": str=\"" << msg.str() << "\", id=" << msg.id() + << ", flags=" << msg.flags() << ", seq_num_uid=" << msg.seq_num_uid() + << ", value="; + PrintTo(msg.value(), os); + *os << '}'; +} + +void PrintTo(const Value& value, std::ostream* os) { + *os << "Value{"; + switch (value.type()) { + case NT_UNASSIGNED: + break; + case NT_BOOLEAN: + *os << (value.GetBoolean() ? "true" : "false"); + break; + case NT_DOUBLE: + *os << value.GetDouble(); + break; + case NT_STRING: + *os << '"' << value.GetString().str() << '"'; + break; + case NT_RAW: + *os << ::testing::PrintToString(value.GetRaw()); + ; + break; + case NT_BOOLEAN_ARRAY: + *os << ::testing::PrintToString(value.GetBooleanArray()); + break; + case NT_DOUBLE_ARRAY: + *os << ::testing::PrintToString(value.GetDoubleArray()); + break; + case NT_STRING_ARRAY: + *os << ::testing::PrintToString(value.GetStringArray()); + break; + case NT_RPC: + *os << ::testing::PrintToString(value.GetRpc()); + break; + default: + *os << "UNKNOWN TYPE " << value.type(); + break; + } + *os << '}'; +} + +} // namespace nt diff --git a/src/test/native/cpp/TestPrinters.h b/src/test/native/cpp/TestPrinters.h new file mode 100644 index 0000000..e5a6358 --- /dev/null +++ b/src/test/native/cpp/TestPrinters.h @@ -0,0 +1,54 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TEST_TESTPRINTERS_H_ +#define NT_TEST_TESTPRINTERS_H_ + +#include +#include + +#include "gtest/gtest.h" + +#include "llvm/StringRef.h" + +namespace llvm { + +inline void PrintTo(StringRef str, ::std::ostream *os) { + ::testing::internal::PrintStringTo(str.str(), os); +} + +} // namespace llvm + +namespace nt { + +class EntryNotification; +class Handle; +class Message; +class Value; + +void PrintTo(const EntryNotification& event, std::ostream* os); +void PrintTo(const Handle& handle, std::ostream* os); + +void PrintTo(const Message& msg, std::ostream* os); + +inline void PrintTo(std::shared_ptr msg, std::ostream* os) { + *os << "shared_ptr{"; + if (msg) PrintTo(*msg, os); + *os << '}'; +} + +void PrintTo(const Value& value, std::ostream* os); + +inline void PrintTo(std::shared_ptr value, std::ostream* os) { + *os << "shared_ptr{"; + if (value) PrintTo(*value, os); + *os << '}'; +} + +} // namespace nt + +#endif // NT_TEST_TESTPRINTERS_H_ diff --git a/src/test/native/cpp/ValueMatcher.cpp b/src/test/native/cpp/ValueMatcher.cpp new file mode 100644 index 0000000..a9d40ae --- /dev/null +++ b/src/test/native/cpp/ValueMatcher.cpp @@ -0,0 +1,33 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#include "ValueMatcher.h" + +#include "TestPrinters.h" + +namespace nt { + +bool ValueMatcher::MatchAndExplain( + std::shared_ptr val, + ::testing::MatchResultListener* listener) const { + if ((!val && goodval) || (val && !goodval) || + (val && goodval && *val != *goodval)) { + return false; + } + return true; +} + +void ValueMatcher::DescribeTo(::std::ostream* os) const { + PrintTo(goodval, os); +} + +void ValueMatcher::DescribeNegationTo(::std::ostream* os) const { + *os << "is not equal to "; + PrintTo(goodval, os); +} + +} // namespace nt diff --git a/src/test/native/cpp/ValueMatcher.h b/src/test/native/cpp/ValueMatcher.h new file mode 100644 index 0000000..981dc76 --- /dev/null +++ b/src/test/native/cpp/ValueMatcher.h @@ -0,0 +1,41 @@ +/*----------------------------------------------------------------------------*/ +/* Copyright (c) FIRST 2017. All Rights Reserved. */ +/* Open Source Software - may be modified and shared by FRC teams. The code */ +/* must be accompanied by the FIRST BSD license file in the root directory of */ +/* the project. */ +/*----------------------------------------------------------------------------*/ + +#ifndef NT_TEST_VALUEMATCHER_H_ +#define NT_TEST_VALUEMATCHER_H_ + +#include +#include + +#include "gmock/gmock.h" + +#include "networktables/NetworkTableValue.h" + +namespace nt { + +class ValueMatcher + : public ::testing::MatcherInterface> { + public: + ValueMatcher(std::shared_ptr goodval_) : goodval(goodval_) {} + + bool MatchAndExplain(std::shared_ptr msg, + ::testing::MatchResultListener* listener) const override; + void DescribeTo(::std::ostream* os) const override; + void DescribeNegationTo(::std::ostream* os) const override; + + private: + std::shared_ptr goodval; +}; + +inline ::testing::Matcher> ValueEq( + std::shared_ptr goodval) { + return ::testing::MakeMatcher(new ValueMatcher(goodval)); +} + +} // namespace nt + +#endif // NT_TEST_VALUEMATCHER_H_ diff --git a/src/test/native/cpp/ValueTest.cpp b/src/test/native/cpp/ValueTest.cpp index e477e8e..442597c 100644 --- a/src/test/native/cpp/ValueTest.cpp +++ b/src/test/native/cpp/ValueTest.cpp @@ -5,10 +5,11 @@ /* the project. */ /*----------------------------------------------------------------------------*/ -#include "nt_Value.h" +#include "networktables/NetworkTableValue.h" #include "Value_internal.h" #include "gtest/gtest.h" +#include "TestPrinters.h" namespace nt { diff --git a/src/test/native/cpp/WireDecoderTest.cpp b/src/test/native/cpp/WireDecoderTest.cpp index 0f4d9eb..9c91a31 100644 --- a/src/test/native/cpp/WireDecoderTest.cpp +++ b/src/test/native/cpp/WireDecoderTest.cpp @@ -8,6 +8,7 @@ #include "WireDecoder.h" #include "gtest/gtest.h" +#include "TestPrinters.h" #include #include @@ -64,21 +65,24 @@ class WireDecoderTest : public ::testing::Test { TEST_F(WireDecoderTest, Construct) { wpi::raw_mem_istream is("", 0); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); EXPECT_EQ(nullptr, d.error()); EXPECT_EQ(0x0300u, d.proto_rev()); } TEST_F(WireDecoderTest, SetProtoRev) { wpi::raw_mem_istream is("", 0); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); d.set_proto_rev(0x0200u); EXPECT_EQ(0x0200u, d.proto_rev()); } TEST_F(WireDecoderTest, Read8) { wpi::raw_mem_istream is("\x05\x01\x00", 3); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); unsigned int val; ASSERT_TRUE(d.Read8(&val)); EXPECT_EQ(5u, val); @@ -92,7 +96,8 @@ TEST_F(WireDecoderTest, Read8) { TEST_F(WireDecoderTest, Read16) { wpi::raw_mem_istream is("\x00\x05\x00\x01\x45\x67\x00\x00", 8); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); unsigned int val; ASSERT_TRUE(d.Read16(&val)); EXPECT_EQ(5u, val); @@ -111,7 +116,8 @@ TEST_F(WireDecoderTest, Read32) { "\x00\x00\x00\x05\x00\x00\x00\x01\x00\x00\xab\xcd" "\x12\x34\x56\x78\x00\x00\x00\x00", 20); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); unsigned long val; ASSERT_TRUE(d.Read32(&val)); EXPECT_EQ(5ul, val); @@ -137,7 +143,8 @@ TEST_F(WireDecoderTest, ReadDouble) { "\x00\x10\x00\x00\x00\x00\x00\x00" "\x7f\xef\xff\xff\xff\xff\xff\xff", 40); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); double val; ASSERT_TRUE(d.ReadDouble(&val)); EXPECT_EQ(0.0, val); @@ -155,7 +162,8 @@ TEST_F(WireDecoderTest, ReadDouble) { TEST_F(WireDecoderTest, ReadUleb128) { wpi::raw_mem_istream is("\x00\x7f\x80\x01\x80", 5); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); unsigned long val; ASSERT_TRUE(d.ReadUleb128(&val)); EXPECT_EQ(0ul, val); @@ -169,7 +177,8 @@ TEST_F(WireDecoderTest, ReadUleb128) { TEST_F(WireDecoderTest, ReadType) { wpi::raw_mem_istream is("\x00\x01\x02\x03\x10\x11\x12\x20", 8); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); NT_Type val; ASSERT_TRUE(d.ReadType(&val)); EXPECT_EQ(NT_BOOLEAN, val); @@ -193,7 +202,8 @@ TEST_F(WireDecoderTest, ReadType) { TEST_F(WireDecoderTest, ReadTypeError) { wpi::raw_mem_istream is("\x30", 1); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); NT_Type val; ASSERT_FALSE(d.ReadType(&val)); EXPECT_EQ(NT_UNASSIGNED, val); @@ -202,7 +212,8 @@ TEST_F(WireDecoderTest, ReadTypeError) { TEST_F(WireDecoderTest, Reset) { wpi::raw_mem_istream is("\x30", 1); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); NT_Type val; ASSERT_FALSE(d.ReadType(&val)); EXPECT_EQ(NT_UNASSIGNED, val); @@ -213,7 +224,8 @@ TEST_F(WireDecoderTest, Reset) { TEST_F(WireDecoderTest, ReadBooleanValue2) { wpi::raw_mem_istream is("\x01\x00", 2); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_BOOLEAN); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_boolean, *val); @@ -232,7 +244,8 @@ TEST_F(WireDecoderTest, ReadDoubleValue2) { "\x3f\xf0\x00\x00\x00\x00\x00\x00" "\x3f\xf0\x00\x00\x00\x00\x00\x00", 16); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_DOUBLE); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_double, *val); @@ -247,7 +260,8 @@ TEST_F(WireDecoderTest, ReadDoubleValue2) { TEST_F(WireDecoderTest, ReadStringValue2) { wpi::raw_mem_istream is("\x00\x05hello\x00\x03" "bye\x55", 13); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_STRING); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_string, *val); @@ -267,7 +281,8 @@ TEST_F(WireDecoderTest, ReadStringValue2) { TEST_F(WireDecoderTest, ReadBooleanArrayValue2) { wpi::raw_mem_istream is("\x03\x00\x01\x00\x02\x01\x00\xff", 8); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_BOOLEAN_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_boolean_array, *val); @@ -286,7 +301,8 @@ TEST_F(WireDecoderTest, ReadBooleanArrayBigValue2) { s.push_back('\xff'); s.append(255, '\x00'); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_BOOLEAN_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_boolean_array_big, *val); @@ -300,7 +316,8 @@ TEST_F(WireDecoderTest, ReadDoubleArrayValue2) { "\x02\x3f\xe0\x00\x00\x00\x00\x00\x00" "\x3f\xd0\x00\x00\x00\x00\x00\x00\x55", 18); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_DOUBLE_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_double_array, *val); @@ -318,7 +335,8 @@ TEST_F(WireDecoderTest, ReadDoubleArrayBigValue2) { s.push_back('\xff'); s.append(255*8, '\x00'); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_DOUBLE_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_double_array_big, *val); @@ -329,7 +347,8 @@ TEST_F(WireDecoderTest, ReadDoubleArrayBigValue2) { TEST_F(WireDecoderTest, ReadStringArrayValue2) { wpi::raw_mem_istream is("\x02\x00\x05hello\x00\x07goodbye\x55", 18); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_STRING_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_string_array, *val); @@ -348,7 +367,8 @@ TEST_F(WireDecoderTest, ReadStringArrayBigValue2) { for (int i=0; i<255; ++i) s.append("\x00\x01h", 3); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); auto val = d.ReadValue(NT_STRING_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_string_array_big, *val); @@ -359,7 +379,8 @@ TEST_F(WireDecoderTest, ReadStringArrayBigValue2) { TEST_F(WireDecoderTest, ReadValueError2) { wpi::raw_mem_istream is("", 0); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); ASSERT_FALSE(d.ReadValue(NT_UNASSIGNED)); // unassigned ASSERT_NE(nullptr, d.error()); @@ -374,7 +395,8 @@ TEST_F(WireDecoderTest, ReadValueError2) { TEST_F(WireDecoderTest, ReadBooleanValue3) { wpi::raw_mem_istream is("\x01\x00", 2); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_BOOLEAN); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_boolean, *val); @@ -393,7 +415,8 @@ TEST_F(WireDecoderTest, ReadDoubleValue3) { "\x3f\xf0\x00\x00\x00\x00\x00\x00" "\x3f\xf0\x00\x00\x00\x00\x00\x00", 16); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_DOUBLE); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_double, *val); @@ -408,7 +431,8 @@ TEST_F(WireDecoderTest, ReadDoubleValue3) { TEST_F(WireDecoderTest, ReadStringValue3) { wpi::raw_mem_istream is("\x05hello\x03" "bye\x55", 11); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_STRING); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_string, *val); @@ -428,7 +452,8 @@ TEST_F(WireDecoderTest, ReadStringValue3) { TEST_F(WireDecoderTest, ReadRawValue3) { wpi::raw_mem_istream is("\x05hello\x03" "bye\x55", 11); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_RAW); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_raw, *val); @@ -448,7 +473,8 @@ TEST_F(WireDecoderTest, ReadRawValue3) { TEST_F(WireDecoderTest, ReadBooleanArrayValue3) { wpi::raw_mem_istream is("\x03\x00\x01\x00\x02\x01\x00\xff", 8); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_BOOLEAN_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_boolean_array, *val); @@ -467,7 +493,8 @@ TEST_F(WireDecoderTest, ReadBooleanArrayBigValue3) { s.push_back('\xff'); s.append(255, '\x00'); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_BOOLEAN_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_boolean_array_big, *val); @@ -481,7 +508,8 @@ TEST_F(WireDecoderTest, ReadDoubleArrayValue3) { "\x02\x3f\xe0\x00\x00\x00\x00\x00\x00" "\x3f\xd0\x00\x00\x00\x00\x00\x00\x55", 18); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_DOUBLE_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_double_array, *val); @@ -499,7 +527,8 @@ TEST_F(WireDecoderTest, ReadDoubleArrayBigValue3) { s.push_back('\xff'); s.append(255*8, '\x00'); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_DOUBLE_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_double_array_big, *val); @@ -510,7 +539,8 @@ TEST_F(WireDecoderTest, ReadDoubleArrayBigValue3) { TEST_F(WireDecoderTest, ReadStringArrayValue3) { wpi::raw_mem_istream is("\x02\x05hello\x07goodbye\x55", 16); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_STRING_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_string_array, *val); @@ -529,7 +559,8 @@ TEST_F(WireDecoderTest, ReadStringArrayBigValue3) { for (int i=0; i<255; ++i) s.append("\x01h", 2); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); auto val = d.ReadValue(NT_STRING_ARRAY); ASSERT_TRUE(bool(val)); EXPECT_EQ(*v_string_array_big, *val); @@ -540,7 +571,8 @@ TEST_F(WireDecoderTest, ReadStringArrayBigValue3) { TEST_F(WireDecoderTest, ReadValueError3) { wpi::raw_mem_istream is("", 0); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); ASSERT_FALSE(d.ReadValue(NT_UNASSIGNED)); // unassigned ASSERT_NE(nullptr, d.error()); } @@ -555,7 +587,8 @@ TEST_F(WireDecoderTest, ReadString2) { s += s_big2; s.push_back('\x55'); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0200u); + wpi::Logger logger; + WireDecoder d(is, 0x0200u, logger); std::string outs; ASSERT_TRUE(d.ReadString(&outs)); EXPECT_EQ(s_normal, outs); @@ -582,7 +615,8 @@ TEST_F(WireDecoderTest, ReadString3) { s += s_big3; s.push_back('\x55'); wpi::raw_mem_istream is(s.data(), s.size()); - WireDecoder d(is, 0x0300u); + wpi::Logger logger; + WireDecoder d(is, 0x0300u, logger); std::string outs; ASSERT_TRUE(d.ReadString(&outs)); EXPECT_EQ(s_normal, outs); diff --git a/src/test/native/cpp/WireEncoderTest.cpp b/src/test/native/cpp/WireEncoderTest.cpp index 39e248c..ec6135f 100644 --- a/src/test/native/cpp/WireEncoderTest.cpp +++ b/src/test/native/cpp/WireEncoderTest.cpp @@ -8,6 +8,7 @@ #include "WireEncoder.h" #include "gtest/gtest.h" +#include "TestPrinters.h" #include #include diff --git a/src/test/native/cpp/main.cpp b/src/test/native/cpp/main.cpp index 0673382..33e2019 100644 --- a/src/test/native/cpp/main.cpp +++ b/src/test/native/cpp/main.cpp @@ -5,20 +5,20 @@ /* the project. */ /*----------------------------------------------------------------------------*/ -#include "gtest/gtest.h" +#include "gmock/gmock.h" -#include "Log.h" +#include -int main(int argc, char **argv) -{ - nt::Logger::GetInstance().SetLogger( - [](unsigned int level, const char* file, unsigned int line, - const char* msg) { - std::fputs(msg, stderr); - std::fputc('\n', stderr); - }); - nt::Logger::GetInstance().set_min_level(0); - ::testing::InitGoogleTest(&argc, argv); - int ret = RUN_ALL_TESTS(); - return ret; +#include "ntcore.h" + +int main(int argc, char** argv) { + nt::AddLogger(nt::GetDefaultInstance(), + [](const nt::LogMessage& msg) { + std::fputs(msg.message.c_str(), stderr); + std::fputc('\n', stderr); + }, + 0, UINT_MAX); + ::testing::InitGoogleMock(&argc, argv); + int ret = RUN_ALL_TESTS(); + return ret; }