From 819c968f3c328a98fe58d48a937b600b7727f87f Mon Sep 17 00:00:00 2001 From: Peter Johnson Date: Mon, 1 Jan 2024 16:41:50 -0800 Subject: [PATCH] WIP loading --- .../glass/support/DataLogReaderThread.h | 26 ++-- sysid/src/main/native/cpp/App.cpp | 20 ++- .../src/main/native/cpp/view/DataSelector.cpp | 118 +++++++++++++++++- sysid/src/main/native/cpp/view/LogLoader.cpp | 22 ++-- .../native/include/sysid/view/DataSelector.h | 30 +++-- .../native/include/sysid/view/LogLoader.h | 6 - .../main/native/include/sysid/view/UILayout.h | 7 +- 7 files changed, 184 insertions(+), 45 deletions(-) diff --git a/glass/src/lib/native/include/glass/support/DataLogReaderThread.h b/glass/src/lib/native/include/glass/support/DataLogReaderThread.h index 5c89b163382..bcbc9623ef8 100644 --- a/glass/src/lib/native/include/glass/support/DataLogReaderThread.h +++ b/glass/src/lib/native/include/glass/support/DataLogReaderThread.h @@ -22,20 +22,22 @@ namespace glass { +class DataLogReaderRange { + public: + DataLogReaderRange(wpi::log::DataLogReader::iterator begin, + wpi::log::DataLogReader::iterator end) + : m_begin{begin}, m_end{end} {} + + wpi::log::DataLogReader::iterator begin() const { return m_begin; } + wpi::log::DataLogReader::iterator end() const { return m_end; } + + wpi::log::DataLogReader::iterator m_begin; + wpi::log::DataLogReader::iterator m_end; +}; + class DataLogReaderEntry : public wpi::log::StartRecordData { public: - struct Range { - Range(wpi::log::DataLogReader::iterator begin, - wpi::log::DataLogReader::iterator end) - : m_begin{begin}, m_end{end} {} - - wpi::log::DataLogReader::iterator begin() const { return m_begin; } - wpi::log::DataLogReader::iterator end() const { return m_end; } - - wpi::log::DataLogReader::iterator m_begin; - wpi::log::DataLogReader::iterator m_end; - }; - std::vector ranges; // ranges where this entry is valid + std::vector ranges; // ranges where this entry is valid }; class DataLogReaderThread { diff --git a/sysid/src/main/native/cpp/App.cpp b/sysid/src/main/native/cpp/App.cpp index fd74ebd8c22..f0ff2b77895 100644 --- a/sysid/src/main/native/cpp/App.cpp +++ b/sysid/src/main/native/cpp/App.cpp @@ -24,6 +24,7 @@ #include #include "sysid/view/Analyzer.h" +#include "sysid/view/DataSelector.h" #include "sysid/view/LogLoader.h" #include "sysid/view/UILayout.h" @@ -32,6 +33,7 @@ namespace gui = wpi::gui; static std::unique_ptr gWindowManager; glass::Window* gLogLoaderWindow; +glass::Window* gDataSelectorWindow; glass::Window* gAnalyzerWindow; glass::Window* gProgramLogWindow; static glass::MainMenuBar gMainMenu; @@ -100,8 +102,16 @@ void Application(std::string_view saveDir) { gWindowManager = std::make_unique(storage); gWindowManager->GlobalInit(); - gLogLoaderWindow = gWindowManager->AddWindow( - "Log Loader", std::make_unique(storage, gLogger)); + auto logLoader = std::make_unique(storage, gLogger); + auto dataSelector = std::make_unique(storage, gLogger); + + logLoader->unload.connect([ds = dataSelector.get()] { ds->Reset(); }); + + gLogLoaderWindow = + gWindowManager->AddWindow("Log Loader", std::move(logLoader)); + + gDataSelectorWindow = + gWindowManager->AddWindow("Data Selector", std::move(dataSelector)); gAnalyzerWindow = gWindowManager->AddWindow( "Analyzer", std::make_unique(storage, gLogger)); @@ -117,6 +127,12 @@ void Application(std::string_view saveDir) { gLogLoaderWindow->SetDefaultSize(sysid::kLogLoaderWindowSize.x, sysid::kLogLoaderWindowSize.y); + // Data selector window position/size + gDataSelectorWindow->SetDefaultPos(sysid::kDataSelectorWindowPos.x, + sysid::kDataSelectorWindowPos.y); + gDataSelectorWindow->SetDefaultSize(sysid::kDataSelectorWindowSize.x, + sysid::kDataSelectorWindowSize.y); + // Analyzer window position/size gAnalyzerWindow->SetDefaultPos(sysid::kAnalyzerWindowPos.x, sysid::kAnalyzerWindowPos.y); diff --git a/sysid/src/main/native/cpp/view/DataSelector.cpp b/sysid/src/main/native/cpp/view/DataSelector.cpp index 036212866d6..8c693cda1fe 100644 --- a/sysid/src/main/native/cpp/view/DataSelector.cpp +++ b/sysid/src/main/native/cpp/view/DataSelector.cpp @@ -5,15 +5,127 @@ #include "sysid/view/DataSelector.h" #include +#include #include +#include +#include using namespace sysid; -void DataSelector::Display() {} +static bool EmitEntryTarget(const char* name, bool isString, + const glass::DataLogReaderEntry** entry) { + if (*entry) { + auto text = + fmt::format("{}: {} ({})", name, (*entry)->name, (*entry)->type); + ImGui::TextUnformatted(text.c_str()); + } else { + ImGui::Text("%s: (%s)", name, + isString ? "string" : "number"); + } + bool rv = false; + if (ImGui::BeginDragDropTarget()) { + if (const ImGuiPayload* payload = ImGui::AcceptDragDropPayload( + isString ? "DataLogEntryString" : "DataLogEntry")) { + assert(payload->DataSize == sizeof(const glass::DataLogReaderEntry*)); + *entry = *static_cast(payload->Data); + rv = true; + } + ImGui::EndDragDropTarget(); + } + return rv; +} -void DataSelector::Reset() { +void DataSelector::Display() { + if (EmitEntryTarget("Test State", true, &m_testStateEntry)) { + LoadTests(*m_testStateEntry); + } + + if (!m_testStateEntry) { + return; + } + + using namespace std::chrono_literals; + if (m_testsFuture.valid() && + m_testsFuture.wait_for(0s) == std::future_status::ready) { + m_tests = m_testsFuture.get(); + } + + if (m_tests.empty()) { + if (m_testsFuture.valid()) { + ImGui::TextUnformatted("Reading tests..."); + } else { + ImGui::TextUnformatted("No tests found"); + } + return; + } + + // Test filtering + if (ImGui::BeginCombo("Test", m_selectedTest.c_str())) { + for (auto&& test : m_tests) { + if (ImGui::Selectable(test.first.c_str(), test.first == m_selectedTest)) { + m_selectedTest = test.first; + } + } + ImGui::EndCombo(); + } + + // DND targets + EmitEntryTarget("Velocity", false, &m_velocityEntry); + EmitEntryTarget("Position", false, &m_positionEntry); + EmitEntryTarget("Voltage", false, &m_voltageEntry); + if (!m_selectedTest.empty() && m_velocityEntry && m_positionEntry && + m_voltageEntry) { + if (ImGui::Button("Load")) { + } + } } -void DataSelector::SetRunStateEntry(const glass::DataLogReaderEntry& entry) { +void DataSelector::Reset() { + m_tests = {}; + m_selectedTest.clear(); + m_testStateEntry = nullptr; + m_velocityEntry = nullptr; + m_positionEntry = nullptr; + m_voltageEntry = nullptr; +} + +void DataSelector::LoadTests(const glass::DataLogReaderEntry& testStateEntry) { + m_testsFuture = std::async(std::launch::async, [&] { + Tests rv; + for (auto&& range : testStateEntry.ranges) { + Runs* curRuns = nullptr; + wpi::log::DataLogReader::iterator lastStart = range.begin(); + for (auto it = range.begin(), end = range.end(); it != end; ++it) { + std::string_view testState; + if (it->GetEntry() != testStateEntry.entry || + !it->GetString(&testState)) { + continue; + } + + if (testState == "none") { + continue; + } + + auto [testName, direction] = wpi::rsplit(testState, '-'); + + // track runs as iterator ranges of the same test + if (curRuns) { + curRuns->emplace_back(lastStart, it); + } + lastStart = it; + auto testIt = rv.find(testName); + if (testIt == rv.end()) { + testIt = rv.emplace(std::string{testName}, State{}).first; + } + auto stateIt = testIt->second.find(testState); + if (stateIt == testIt->second.end()) { + stateIt = + testIt->second.emplace(std::string{testState}, Runs{}).first; + } + curRuns = &stateIt->second; + } + } + return rv; + }); } diff --git a/sysid/src/main/native/cpp/view/LogLoader.cpp b/sysid/src/main/native/cpp/view/LogLoader.cpp index d3805ca0951..aa811fc5bf4 100644 --- a/sysid/src/main/native/cpp/view/LogLoader.cpp +++ b/sysid/src/main/native/cpp/view/LogLoader.cpp @@ -89,14 +89,12 @@ void LogLoader::Display() { } ImGui::BeginTable( - "Entries", 1, + "Entries", 2, ImGuiTableFlags_Borders | ImGuiTableFlags_SizingStretchProp); -#if 0 ImGui::TableSetupColumn("Name"); ImGui::TableSetupColumn("Type"); - ImGui::TableSetupColumn("Metadata"); + // ImGui::TableSetupColumn("Metadata"); ImGui::TableHeadersRow(); -#endif DisplayEntryTree(m_entryTree); ImGui::EndTable(); } @@ -104,8 +102,9 @@ void LogLoader::Display() { void LogLoader::RebuildEntryTree() { wpi::SmallVector parts; m_reader->ForEachEntryName([&](const glass::DataLogReaderEntry& entry) { - // only show double/float entries (TODO: support struct/protobuf) - if (entry.type != "double" && entry.type != "float") { + // only show double/float/string entries (TODO: support struct/protobuf) + if (entry.type != "double" && entry.type != "float" && + entry.type != "string") { return; } @@ -160,17 +159,18 @@ static void EmitEntry(const std::string& name, ImGui::Selectable(name.c_str()); if (ImGui::BeginDragDropSource()) { auto entryPtr = &entry; - ImGui::SetDragDropPayload("DataLogEntry", &entryPtr, - sizeof(entryPtr)); // NOLINT + ImGui::SetDragDropPayload( + entry.type == "string" ? "DataLogEntryString" : "DataLogEntry", + &entryPtr, + sizeof(entryPtr)); // NOLINT ImGui::TextUnformatted(entry.name.data(), entry.name.data() + entry.name.size()); ImGui::EndDragDropSource(); } -#if 0 ImGui::TableNextColumn(); ImGui::TextUnformatted(entry.type.data(), entry.type.data() + entry.type.size()); - +#if 0 ImGui::TableNextColumn(); ImGui::TextUnformatted(entry.metadata.data(), entry.metadata.data() + entry.metadata.size()); @@ -187,8 +187,8 @@ void LogLoader::DisplayEntryTree(const std::vector& tree) { ImGui::TableNextColumn(); bool open = ImGui::TreeNodeEx(node.name.c_str(), ImGuiTreeNodeFlags_SpanFullWidth); -#if 0 ImGui::TableNextColumn(); +#if 0 ImGui::TableNextColumn(); #endif if (open) { diff --git a/sysid/src/main/native/include/sysid/view/DataSelector.h b/sysid/src/main/native/include/sysid/view/DataSelector.h index 744e14e51d1..a903667c508 100644 --- a/sysid/src/main/native/include/sysid/view/DataSelector.h +++ b/sysid/src/main/native/include/sysid/view/DataSelector.h @@ -4,7 +4,14 @@ #pragma once +#include +#include +#include +#include + #include +#include +#include namespace glass { class DataLogReaderEntry; @@ -27,7 +34,7 @@ class DataSelector : public glass::View { * @param logger The program logger */ explicit DataSelector(glass::Storage& storage, wpi::Logger& logger) - : m_logger(logger) {} + /*: m_logger{logger}*/ {} /** * Displays the log loader window. @@ -40,14 +47,19 @@ class DataSelector : public glass::View { */ void Reset(); - /** - * Sets the run state entry. - * - * @param entry - */ - void SetRunStateEntry(const glass::DataLogReaderEntry& entry); - private: - wpi::Logger& m_logger; + void LoadTests(const glass::DataLogReaderEntry& testStateEntry); + + // wpi::Logger& m_logger; + using Runs = std::vector; + using State = std::map>; // full name + using Tests = std::map>; // e.g. "dynamic" + std::future m_testsFuture; + std::string m_selectedTest; + Tests m_tests; + const glass::DataLogReaderEntry* m_testStateEntry = nullptr; + const glass::DataLogReaderEntry* m_velocityEntry = nullptr; + const glass::DataLogReaderEntry* m_positionEntry = nullptr; + const glass::DataLogReaderEntry* m_voltageEntry = nullptr; }; } // namespace sysid diff --git a/sysid/src/main/native/include/sysid/view/LogLoader.h b/sysid/src/main/native/include/sysid/view/LogLoader.h index e629e3d6412..dc82a393093 100644 --- a/sysid/src/main/native/include/sysid/view/LogLoader.h +++ b/sysid/src/main/native/include/sysid/view/LogLoader.h @@ -51,12 +51,6 @@ class LogLoader : public glass::View { */ wpi::sig::Signal<> unload; - /** - * Signal called when a new file is fully loaded. - * The parameter is the LogEntry for the analysis state string entry. - */ - wpi::sig::Signal loaded; - private: wpi::Logger& m_logger; diff --git a/sysid/src/main/native/include/sysid/view/UILayout.h b/sysid/src/main/native/include/sysid/view/UILayout.h index 25d4436e4f4..2699cd86a1e 100644 --- a/sysid/src/main/native/include/sysid/view/UILayout.h +++ b/sysid/src/main/native/include/sysid/view/UILayout.h @@ -63,8 +63,11 @@ inline constexpr Vector2d kLeftColSize{ // Left column contents inline constexpr Vector2d kLogLoaderWindowPos = kLeftColPos; -inline constexpr Vector2d kLogLoaderWindowSize{ - kLeftColSize.x, kAppWindowSize.y - kWindowGap - kLogLoaderWindowPos.y}; +inline constexpr Vector2d kLogLoaderWindowSize{kLeftColSize.x, 550}; +inline constexpr Vector2d kDataSelectorWindowPos = + kLogLoaderWindowPos + Vector2d{0, kLogLoaderWindowSize.y + kWindowGap}; +inline constexpr Vector2d kDataSelectorWindowSize{ + kLeftColSize.x, kAppWindowSize.y - kWindowGap - kDataSelectorWindowPos.y}; // Center column position and size inline constexpr Vector2d kCenterColPos =