Skip to content
This repository has been archived by the owner on Sep 21, 2020. It is now read-only.

Commit

Permalink
Implement independent instances.
Browse files Browse the repository at this point in the history
Previously, most of the classes were implemented as singletons so only one
instance was possible.

This change adds an instance handle-based API.  In Java, this API is located
in a different package than the old API (edu.wpi.first.networktables).

Backwards compatibility with ITable and the old NetworkTable API is largely
maintained, but a handful of classes have moved to the new package in Java
(ConnectionInfo and PersistentException), and the old JNI has been completed
replaced.

Also:
- Move SetTeam implementation to Dispatcher.
- Consistently pass time through Java and C++ Value API.
- Rename nt_Value.h to NetworkTableValue.h for consistency with Java.
- Improve documentation
- Make C++ and Java APIs more consistent
- Document RPC functions and support RPC in Java.
- Add polling features for entry and connection listeners and use them to
  move callback threads to Java level.
- Remove thread start and stop hooks (as polling is available).
- Make Notifiers, RpcServer, Dispatcher, and Storage mockable.
- Set NOTIFY_NEW on immediate entry notifications.
- Make GetTable("/") and GetTable("") equivalent.
- Generate local notification for flags update when loading persistent file.

And many unit test updates/changes:
- Use InitGoogleMock instead of InitGoogleTest in test main.
- Move test printers to TestPrinter.h/cpp.
- Provide printers for StringRef, EntryNotifier, and Handle.
- StorageTest: Check notifications.
- Add entry notifier unit tests.
- Storage: Add test for incoming entry assignment.
- Update connection listener tests.
- Add entry listener unit tests.

Fixes #11, #140, #189, #190, #192, #193, #221
  • Loading branch information
PeterJohnson committed Sep 7, 2017
1 parent 8125a17 commit 5ab20bb
Show file tree
Hide file tree
Showing 118 changed files with 15,068 additions and 4,070 deletions.
4 changes: 2 additions & 2 deletions build.gradle
Original file line number Diff line number Diff line change
Expand Up @@ -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 ]
}
Expand Down
35 changes: 22 additions & 13 deletions manualTests/native/client.cpp
Original file line number Diff line number Diff line change
@@ -1,25 +1,34 @@
#include <chrono>
#include <climits>
#include <cstdio>
#include <thread>

#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));
}
64 changes: 38 additions & 26 deletions manualTests/native/rpc_local.cpp
Original file line number Diff line number Diff line change
@@ -1,48 +1,60 @@
#include <chrono>
#include <climits>
#include <cstdio>
#include <thread>

#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<double>();
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<double>());

return 0;
}
50 changes: 31 additions & 19 deletions manualTests/native/rpc_speed.cpp
Original file line number Diff line number Diff line change
@@ -1,38 +1,50 @@
#include <chrono>
#include <climits>
#include <cstdio>
#include <thread>
#include <iostream>

#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<double>();
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;
}
}
Expand Down
31 changes: 19 additions & 12 deletions manualTests/native/server.cpp
Original file line number Diff line number Diff line change
@@ -1,24 +1,31 @@
#include <chrono>
#include <climits>
#include <cstdio>
#include <thread>

#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));
}
4 changes: 2 additions & 2 deletions src/dev/java/edu/wpi/first/ntcore/DevMain.java
Original file line number Diff line number Diff line change
@@ -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());
}
}
7 changes: 4 additions & 3 deletions src/dev/native/cpp/main.cpp
Original file line number Diff line number Diff line change
@@ -1,9 +1,10 @@
#include "ntcore.h"
#include "nt_Value.h"
#include <iostream>

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;
}
57 changes: 57 additions & 0 deletions src/main/java/edu/wpi/first/networktables/ConnectionInfo.java
Original file line number Diff line number Diff line change
@@ -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;
}
}
Original file line number Diff line number Diff line change
@@ -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;
}
}
Loading

0 comments on commit 5ab20bb

Please sign in to comment.