2020-12-26 14:31:24 -08:00
|
|
|
// Copyright (c) FIRST and other WPILib contributors.
|
|
|
|
|
// Open Source Software; you can modify and/or share it under the terms of
|
|
|
|
|
// the WPILib BSD license file in the root directory of this project.
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
#include "glass/networktables/NetworkTablesProvider.h"
|
|
|
|
|
|
|
|
|
|
#include <algorithm>
|
|
|
|
|
|
|
|
|
|
#include <ntcore_cpp.h>
|
|
|
|
|
#include <wpi/SmallString.h>
|
|
|
|
|
#include <wpigui.h>
|
|
|
|
|
|
|
|
|
|
using namespace glass;
|
|
|
|
|
|
|
|
|
|
NetworkTablesProvider::NetworkTablesProvider(const wpi::Twine& iniName)
|
|
|
|
|
: NetworkTablesProvider{iniName, nt::GetDefaultInstance()} {}
|
|
|
|
|
|
|
|
|
|
NetworkTablesProvider::NetworkTablesProvider(const wpi::Twine& iniName,
|
|
|
|
|
NT_Inst inst)
|
|
|
|
|
: Provider{iniName + "Window"}, m_nt{inst}, m_typeCache{iniName} {
|
|
|
|
|
m_nt.AddListener("", NT_NOTIFY_LOCAL | NT_NOTIFY_NEW | NT_NOTIFY_DELETE |
|
|
|
|
|
NT_NOTIFY_IMMEDIATE);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesProvider::GlobalInit() {
|
|
|
|
|
Provider::GlobalInit();
|
|
|
|
|
wpi::gui::AddInit([this] { m_typeCache.Initialize(); });
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesProvider::DisplayMenu() {
|
|
|
|
|
wpi::SmallVector<wpi::StringRef, 6> path;
|
|
|
|
|
wpi::SmallString<64> name;
|
|
|
|
|
for (auto&& entry : m_viewEntries) {
|
|
|
|
|
path.clear();
|
|
|
|
|
wpi::StringRef{entry->name}.split(path, '/', -1, false);
|
|
|
|
|
|
|
|
|
|
bool fullDepth = true;
|
|
|
|
|
int depth = 0;
|
|
|
|
|
for (; depth < (static_cast<int>(path.size()) - 1); ++depth) {
|
|
|
|
|
name = path[depth];
|
|
|
|
|
if (!ImGui::BeginMenu(name.c_str())) {
|
|
|
|
|
fullDepth = false;
|
|
|
|
|
break;
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
if (fullDepth) {
|
|
|
|
|
bool visible = entry->window && entry->window->IsVisible();
|
|
|
|
|
bool wasVisible = visible;
|
|
|
|
|
// FIXME: enabled?
|
|
|
|
|
// data is the last item, so is guaranteed to be null-terminated
|
|
|
|
|
ImGui::MenuItem(path.back().data(), nullptr, &visible, true);
|
|
|
|
|
if (!wasVisible && visible) {
|
|
|
|
|
Show(entry.get(), entry->window);
|
|
|
|
|
} else if (wasVisible && !visible && entry->window) {
|
|
|
|
|
entry->window->SetVisible(false);
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
2020-12-28 12:58:06 -08:00
|
|
|
for (; depth > 0; --depth) {
|
|
|
|
|
ImGui::EndMenu();
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesProvider::Update() {
|
|
|
|
|
Provider::Update();
|
|
|
|
|
|
|
|
|
|
// add/remove entries from NT changes
|
|
|
|
|
for (auto&& event : m_nt.PollListener()) {
|
|
|
|
|
// look for .type fields
|
|
|
|
|
wpi::StringRef eventName{event.name};
|
|
|
|
|
if (!eventName.endswith("/.type") || !event.value ||
|
2020-12-28 12:58:06 -08:00
|
|
|
!event.value->IsString()) {
|
2020-09-12 10:55:46 -07:00
|
|
|
continue;
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
auto tableName = eventName.drop_back(6);
|
|
|
|
|
|
|
|
|
|
// only handle ones where we have a builder
|
|
|
|
|
auto builderIt = m_typeMap.find(event.value->GetString());
|
2020-12-28 12:58:06 -08:00
|
|
|
if (builderIt == m_typeMap.end()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
if (event.flags & NT_NOTIFY_DELETE) {
|
|
|
|
|
auto it = std::find_if(
|
|
|
|
|
m_viewEntries.begin(), m_viewEntries.end(), [&](const auto& elem) {
|
|
|
|
|
return static_cast<Entry*>(elem->modelEntry)->typeEntry ==
|
|
|
|
|
event.entry;
|
|
|
|
|
});
|
|
|
|
|
if (it != m_viewEntries.end()) {
|
|
|
|
|
m_viewEntries.erase(it);
|
|
|
|
|
}
|
|
|
|
|
} else if (event.flags & NT_NOTIFY_NEW) {
|
|
|
|
|
GetOrCreateView(builderIt->second, event.entry, tableName);
|
|
|
|
|
// cache the type
|
|
|
|
|
m_typeCache[tableName].SetName(event.value->GetString());
|
|
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// check for visible windows that need displays (typically this is due to
|
|
|
|
|
// file loading)
|
|
|
|
|
for (auto&& window : m_windows) {
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!window->IsVisible() || window->HasView()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
auto id = window->GetId();
|
|
|
|
|
auto typeIt = m_typeCache.find(id);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (typeIt == m_typeCache.end()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
// only handle ones where we have a builder
|
|
|
|
|
auto builderIt = m_typeMap.find(typeIt->second.GetName());
|
2020-12-28 12:58:06 -08:00
|
|
|
if (builderIt == m_typeMap.end()) {
|
|
|
|
|
continue;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
auto entry = GetOrCreateView(
|
|
|
|
|
builderIt->second, nt::GetEntry(m_nt.GetInstance(), id + "/.type"), id);
|
2020-12-28 12:58:06 -08:00
|
|
|
if (entry) {
|
|
|
|
|
Show(entry, window.get());
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
}
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesProvider::Register(wpi::StringRef typeName,
|
|
|
|
|
CreateModelFunc createModel,
|
|
|
|
|
CreateViewFunc createView) {
|
|
|
|
|
m_typeMap[typeName] = Builder{std::move(createModel), std::move(createView)};
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
void NetworkTablesProvider::Show(ViewEntry* entry, Window* window) {
|
|
|
|
|
// if there's already a window, just show it
|
|
|
|
|
if (entry->window) {
|
|
|
|
|
entry->window->SetVisible(true);
|
|
|
|
|
return;
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get or create model
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!entry->modelEntry->model) {
|
2020-09-12 10:55:46 -07:00
|
|
|
entry->modelEntry->model =
|
|
|
|
|
entry->modelEntry->createModel(m_nt.GetInstance(), entry->name.c_str());
|
2020-12-28 12:58:06 -08:00
|
|
|
}
|
|
|
|
|
if (!entry->modelEntry->model) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
|
|
|
|
|
// the window might exist and we're just not associated to it yet
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!window) {
|
|
|
|
|
window = GetOrAddWindow(entry->name, true);
|
|
|
|
|
}
|
|
|
|
|
if (!window) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
entry->window = window;
|
|
|
|
|
|
|
|
|
|
// create view
|
|
|
|
|
auto view = entry->createView(window, entry->modelEntry->model.get(),
|
|
|
|
|
entry->name.c_str());
|
2020-12-28 12:58:06 -08:00
|
|
|
if (!view) {
|
|
|
|
|
return;
|
|
|
|
|
}
|
2020-09-12 10:55:46 -07:00
|
|
|
window->SetView(std::move(view));
|
|
|
|
|
|
|
|
|
|
entry->window->SetVisible(true);
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
NetworkTablesProvider::ViewEntry* NetworkTablesProvider::GetOrCreateView(
|
|
|
|
|
const Builder& builder, NT_Entry typeEntry, wpi::StringRef name) {
|
|
|
|
|
// get view entry if it already exists
|
|
|
|
|
auto viewIt = FindViewEntry(name);
|
|
|
|
|
if (viewIt != m_viewEntries.end() && (*viewIt)->name == name) {
|
|
|
|
|
// make sure typeEntry is set in model
|
|
|
|
|
static_cast<Entry*>((*viewIt)->modelEntry)->typeEntry = typeEntry;
|
|
|
|
|
return viewIt->get();
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// get or create model entry
|
|
|
|
|
auto modelIt = FindModelEntry(name);
|
|
|
|
|
if (modelIt != m_modelEntries.end() && (*modelIt)->name == name) {
|
|
|
|
|
static_cast<Entry*>(modelIt->get())->typeEntry = typeEntry;
|
|
|
|
|
} else {
|
|
|
|
|
modelIt = m_modelEntries.emplace(
|
|
|
|
|
modelIt, std::make_unique<Entry>(typeEntry, name, builder));
|
|
|
|
|
}
|
|
|
|
|
|
|
|
|
|
// create new view entry
|
|
|
|
|
viewIt = m_viewEntries.emplace(
|
|
|
|
|
viewIt,
|
|
|
|
|
std::make_unique<ViewEntry>(
|
|
|
|
|
name, modelIt->get(), [](Model*, const char*) { return true; },
|
|
|
|
|
builder.createView));
|
|
|
|
|
|
|
|
|
|
return viewIt->get();
|
|
|
|
|
}
|