mirror of
https://github.com/wpilibsuite/allwpilib
synced 2026-06-19 00:41:43 +00:00
[robotpy] Add wrapper for timestamp functions, like SetNowImpl (#8889)
`SetNowImpl` is used somewhat often in unit tests. It is a little bit goofier to wrap because it takes a C function, so a little bit more work has to be done to get that wrapped in pybind. Claude helped.
This commit is contained in:
8
wpiutil/robotpy_pybind_build_info.bzl
generated
8
wpiutil/robotpy_pybind_build_info.bzl
generated
@@ -68,6 +68,14 @@ def wpiutil_extension(srcs = [], header_to_dat_deps = [], extra_hdrs = [], inclu
|
||||
tmpl_class_names = [],
|
||||
trampolines = [],
|
||||
),
|
||||
struct(
|
||||
class_name = "Timestamp",
|
||||
yml_file = "semiwrap/Timestamp.yml",
|
||||
header_root = "$(execpath :robotpy-native-wpiutil.copy_headers)",
|
||||
header_file = "$(execpath :robotpy-native-wpiutil.copy_headers)/wpi/util/timestamp.hpp",
|
||||
tmpl_class_names = [],
|
||||
trampolines = [],
|
||||
),
|
||||
struct(
|
||||
class_name = "Sendable",
|
||||
yml_file = "semiwrap/Sendable.yml",
|
||||
|
||||
@@ -75,6 +75,7 @@ Synchronization = "wpi/util/Synchronization.hpp"
|
||||
PixelFormat = "wpi/util/PixelFormat.hpp"
|
||||
RawFrame_c = "wpi/util/RawFrame.h"
|
||||
RawFrame = "wpi/util/RawFrame.hpp"
|
||||
Timestamp = "wpi/util/timestamp.hpp"
|
||||
|
||||
# wpi/sendable
|
||||
Sendable = "wpi/util/sendable/Sendable.hpp"
|
||||
|
||||
6
wpiutil/src/main/python/semiwrap/Timestamp.yml
Normal file
6
wpiutil/src/main/python/semiwrap/Timestamp.yml
Normal file
@@ -0,0 +1,6 @@
|
||||
functions:
|
||||
NowDefault:
|
||||
SetNowImpl:
|
||||
ignore: true # This is more complicated, so it is handled by src/timestmap.cpp
|
||||
Now:
|
||||
GetSystemTime:
|
||||
@@ -4,10 +4,14 @@ from . import _init__wpiutil
|
||||
from ._wpiutil import (
|
||||
Color,
|
||||
Color8Bit,
|
||||
getSystemTime,
|
||||
now,
|
||||
nowDefault,
|
||||
PixelFormat,
|
||||
Sendable,
|
||||
SendableBuilder,
|
||||
SendableRegistry,
|
||||
SetNowImpl,
|
||||
TimestampSource,
|
||||
getStackTrace,
|
||||
getStackTraceDefault,
|
||||
@@ -16,10 +20,14 @@ from ._wpiutil import (
|
||||
__all__ = [
|
||||
"Color",
|
||||
"Color8Bit",
|
||||
"getSystemTime",
|
||||
"now",
|
||||
"nowDefault",
|
||||
"PixelFormat",
|
||||
"Sendable",
|
||||
"SendableBuilder",
|
||||
"SendableRegistry",
|
||||
"SetNowImpl",
|
||||
"TimestampSource",
|
||||
"getStackTrace",
|
||||
"getStackTraceDefault",
|
||||
|
||||
@@ -7,6 +7,9 @@ void cleanup_stack_trace_hook();
|
||||
void setup_safethread_gil();
|
||||
void cleanup_safethread_gil();
|
||||
|
||||
void set_now_impl(py::object fn);
|
||||
void cleanup_now_impl();
|
||||
|
||||
#ifndef __FIRST_SYSTEMCORE__
|
||||
|
||||
namespace wpi::util::impl {
|
||||
@@ -32,10 +35,12 @@ SEMIWRAP_PYBIND11_MODULE(m) {
|
||||
cleanup_sendable_registry();
|
||||
cleanup_stack_trace_hook();
|
||||
cleanup_safethread_gil();
|
||||
cleanup_now_impl();
|
||||
});
|
||||
|
||||
setup_safethread_gil();
|
||||
|
||||
m.def("_setup_stack_trace_hook", &setup_stack_trace_hook);
|
||||
m.def("SetNowImpl", &set_now_impl);
|
||||
m.add_object("_st_cleanup", cleanup);
|
||||
}
|
||||
|
||||
46
wpiutil/src/main/python/wpiutil/src/timestamp.cpp
Normal file
46
wpiutil/src/main/python/wpiutil/src/timestamp.cpp
Normal file
@@ -0,0 +1,46 @@
|
||||
#include <pybind11/pybind11.h>
|
||||
#include <pybind11/functional.h>
|
||||
|
||||
#include "wpi/util/timestamp.hpp"
|
||||
|
||||
namespace py = pybind11;
|
||||
|
||||
py::object &get_now_impl_ref() {
|
||||
static py::object get_now_impl_ref;
|
||||
return get_now_impl_ref;
|
||||
}
|
||||
|
||||
// Helper function to massage the python callback into a C api
|
||||
uint64_t now_impl_trampoline() {
|
||||
py::gil_scoped_acquire acquire;
|
||||
try {
|
||||
auto &hook = get_now_impl_ref();
|
||||
if (hook) {
|
||||
return hook().cast<uint64_t>();
|
||||
}
|
||||
} catch (py::error_already_set &e) {
|
||||
e.discard_as_unraisable("wpiutil.now_impl_trampoline");
|
||||
}
|
||||
|
||||
return wpi::util::NowDefault();
|
||||
}
|
||||
|
||||
void set_now_impl(py::object func) {
|
||||
get_now_impl_ref() = func;
|
||||
if (func.is_none()) {
|
||||
wpi::util::SetNowImpl(nullptr);
|
||||
} else {
|
||||
wpi::util::SetNowImpl(&now_impl_trampoline);
|
||||
}
|
||||
}
|
||||
|
||||
void cleanup_now_impl() {
|
||||
wpi::util::SetNowImpl(nullptr);
|
||||
|
||||
// release the function during interpreter shutdown
|
||||
auto &hook = get_now_impl_ref();
|
||||
if (hook) {
|
||||
hook.dec_ref();
|
||||
hook.release();
|
||||
}
|
||||
}
|
||||
46
wpiutil/src/test/python/test_timestamp.py
Normal file
46
wpiutil/src/test/python/test_timestamp.py
Normal file
@@ -0,0 +1,46 @@
|
||||
|
||||
import wpiutil
|
||||
import time
|
||||
import pytest
|
||||
|
||||
def test_default():
|
||||
wpi_now = wpiutil.now() * 1e-6
|
||||
py_now = int(time.time())
|
||||
|
||||
# Allow a one second delta. We don't care about it being all that accurate in the
|
||||
# test, just that we are in the same galaxy
|
||||
assert py_now == pytest.approx(wpi_now, abs=1)
|
||||
|
||||
|
||||
NOW_TIMESTAMP_S = 0
|
||||
|
||||
def custom_now_getter():
|
||||
global NOW_TIMESTAMP_S
|
||||
return int(NOW_TIMESTAMP_S * 1e6)
|
||||
|
||||
|
||||
@pytest.fixture
|
||||
def custom_fixture():
|
||||
wpiutil.SetNowImpl(custom_now_getter)
|
||||
yield
|
||||
wpiutil.SetNowImpl(None)
|
||||
|
||||
|
||||
def test_custom_timestamp(custom_fixture):
|
||||
global NOW_TIMESTAMP_S
|
||||
|
||||
assert 0 == wpiutil.now()
|
||||
|
||||
NOW_TIMESTAMP_S = 1.5
|
||||
assert 1_500_000 == wpiutil.now()
|
||||
|
||||
NOW_TIMESTAMP_S = 100
|
||||
assert 100_000_000 == wpiutil.now()
|
||||
|
||||
# Set it back to the standard implementation and expect its roughly milliseconds since 1970
|
||||
wpiutil.SetNowImpl(None)
|
||||
wpi_now = wpiutil.now() * 1e-6
|
||||
py_now = int(time.time())
|
||||
assert py_now == pytest.approx(wpi_now, abs=1)
|
||||
|
||||
|
||||
Reference in New Issue
Block a user