diff --git a/gen/SmartDashboard.yml b/gen/SmartDashboard.yml index bfa9a62ae..4300481ec 100644 --- a/gen/SmartDashboard.yml +++ b/gen/SmartDashboard.yml @@ -1,5 +1,8 @@ --- +extra_includes: +- src/rpy/SmartDashboardData.h + classes: SmartDashboard: nodelete: true @@ -21,13 +24,38 @@ classes: Delete: GetEntry: PutData: + # overrides ensure data doesn't die if this is the only reference overloads: wpi::StringRef, Sendable*: - # keepalive: - # - [1, 3] + cpp_code: | + [](py::str &key, std::shared_ptr data) { + + // convert key to a raw string so that we can create a StringRef + Py_ssize_t raw_size; + const char *raw_str = PyUnicode_AsUTF8AndSize(key.ptr(), &raw_size); + if (raw_str == NULL) { + throw py::error_already_set(); + } + + wpi::StringRef keyRef(raw_str, raw_size); + frc::SmartDashboard::PutData(keyRef, data.get()); + + // this comes after the PutData to ensure that the original object doesn't die + // while PutData is called + rpy::addSmartDashboardData(key, data); + } Sendable*: - # keepalive: - # - [1, 2] + cpp_code: | + [](std::shared_ptr value) { + frc::SmartDashboard::PutData(value.get()); + // this comes after the PutData to ensure that the original object doesn't die + // while PutData is called + auto name = SendableRegistry::GetInstance().GetName(value.get()); + if (!name.empty()) { + py::str key(name); + rpy::addSmartDashboardData(key, value); + } + } GetData: PutBoolean: SetDefaultBoolean: @@ -139,3 +167,11 @@ classes: GetValue: PostListenerTask: UpdateValues: + +inline_code: | + // ensure that the smart dashboard data is released when python shuts down + static int unused; // the capsule needs something to reference + py::capsule cleanup(&unused, [](void *) { + rpy::clearSmartDashboardData(); + }); + m.add_object("_sd_cleanup", cleanup); \ No newline at end of file diff --git a/pyproject.toml b/pyproject.toml index 3f58812dd..a7abc41e9 100644 --- a/pyproject.toml +++ b/pyproject.toml @@ -30,6 +30,7 @@ sources = [ "wpilib/src/main.cpp", "wpilib/src/rpy/ControlWord.cpp", "wpilib/src/rpy/Notifier.cpp", + "wpilib/src/rpy/SmartDashboardData.cpp", "wpilib/src/rpy/SpeedControllerGroup.cpp", ] diff --git a/tests/test_wpilib.py b/tests/test_wpilib.py index 35ee10a83..8c93187b0 100644 --- a/tests/test_wpilib.py +++ b/tests/test_wpilib.py @@ -1,4 +1,5 @@ import wpilib +import weakref def test_sendable_chooser(): @@ -7,3 +8,12 @@ def test_sendable_chooser(): chooser.setDefaultOption("option", True) assert chooser.getSelected() is True + + +def test_smart_dashboard_putdata(): + t = wpilib.Talon(4) + ref = weakref.ref(t) + wpilib.SmartDashboard.putData("talon", t) + del t + assert bool(ref) is True + assert wpilib.SmartDashboard.getData("talon") is ref() diff --git a/wpilib/src/rpy/SmartDashboardData.cpp b/wpilib/src/rpy/SmartDashboardData.cpp new file mode 100644 index 000000000..a7520ca2e --- /dev/null +++ b/wpilib/src/rpy/SmartDashboardData.cpp @@ -0,0 +1,33 @@ + +#include "SmartDashboardData.h" + +namespace rpy { + +// +// Ensures that python objects added to the SmartDashboard have at least one +// reference to them +// +// All functions here must be called with the GIL held +// + +static py::dict &getSmartDashboardData() { + static py::dict data; + return data; +} + +void addSmartDashboardData(py::str &key, std::shared_ptr data) { + auto &sdData = getSmartDashboardData(); + sdData[key] = py::cast(data); +} + +void clearSmartDashboardData() { + auto &sdData = getSmartDashboardData(); + if (sdData) { + sdData.clear(); + // force the dictionary to be deleted otherwise it'll crash when libc++ + // is unwinding static objects after interpreter destruction + sdData.dec_ref(); + } +} + +} // namespace rpy diff --git a/wpilib/src/rpy/SmartDashboardData.h b/wpilib/src/rpy/SmartDashboardData.h new file mode 100644 index 000000000..2729da834 --- /dev/null +++ b/wpilib/src/rpy/SmartDashboardData.h @@ -0,0 +1,16 @@ + +#pragma once + +#include +#include + +namespace rpy { + +// +// These functions must be called with the GIL held +// + +void addSmartDashboardData(py::str &key, std::shared_ptr data); +void clearSmartDashboardData(); + +} // namespace rpy \ No newline at end of file