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 = [],
|
tmpl_class_names = [],
|
||||||
trampolines = [],
|
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(
|
struct(
|
||||||
class_name = "Sendable",
|
class_name = "Sendable",
|
||||||
yml_file = "semiwrap/Sendable.yml",
|
yml_file = "semiwrap/Sendable.yml",
|
||||||
|
|||||||
@@ -75,6 +75,7 @@ Synchronization = "wpi/util/Synchronization.hpp"
|
|||||||
PixelFormat = "wpi/util/PixelFormat.hpp"
|
PixelFormat = "wpi/util/PixelFormat.hpp"
|
||||||
RawFrame_c = "wpi/util/RawFrame.h"
|
RawFrame_c = "wpi/util/RawFrame.h"
|
||||||
RawFrame = "wpi/util/RawFrame.hpp"
|
RawFrame = "wpi/util/RawFrame.hpp"
|
||||||
|
Timestamp = "wpi/util/timestamp.hpp"
|
||||||
|
|
||||||
# wpi/sendable
|
# wpi/sendable
|
||||||
Sendable = "wpi/util/sendable/Sendable.hpp"
|
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 (
|
from ._wpiutil import (
|
||||||
Color,
|
Color,
|
||||||
Color8Bit,
|
Color8Bit,
|
||||||
|
getSystemTime,
|
||||||
|
now,
|
||||||
|
nowDefault,
|
||||||
PixelFormat,
|
PixelFormat,
|
||||||
Sendable,
|
Sendable,
|
||||||
SendableBuilder,
|
SendableBuilder,
|
||||||
SendableRegistry,
|
SendableRegistry,
|
||||||
|
SetNowImpl,
|
||||||
TimestampSource,
|
TimestampSource,
|
||||||
getStackTrace,
|
getStackTrace,
|
||||||
getStackTraceDefault,
|
getStackTraceDefault,
|
||||||
@@ -16,10 +20,14 @@ from ._wpiutil import (
|
|||||||
__all__ = [
|
__all__ = [
|
||||||
"Color",
|
"Color",
|
||||||
"Color8Bit",
|
"Color8Bit",
|
||||||
|
"getSystemTime",
|
||||||
|
"now",
|
||||||
|
"nowDefault",
|
||||||
"PixelFormat",
|
"PixelFormat",
|
||||||
"Sendable",
|
"Sendable",
|
||||||
"SendableBuilder",
|
"SendableBuilder",
|
||||||
"SendableRegistry",
|
"SendableRegistry",
|
||||||
|
"SetNowImpl",
|
||||||
"TimestampSource",
|
"TimestampSource",
|
||||||
"getStackTrace",
|
"getStackTrace",
|
||||||
"getStackTraceDefault",
|
"getStackTraceDefault",
|
||||||
|
|||||||
@@ -7,6 +7,9 @@ void cleanup_stack_trace_hook();
|
|||||||
void setup_safethread_gil();
|
void setup_safethread_gil();
|
||||||
void cleanup_safethread_gil();
|
void cleanup_safethread_gil();
|
||||||
|
|
||||||
|
void set_now_impl(py::object fn);
|
||||||
|
void cleanup_now_impl();
|
||||||
|
|
||||||
#ifndef __FIRST_SYSTEMCORE__
|
#ifndef __FIRST_SYSTEMCORE__
|
||||||
|
|
||||||
namespace wpi::util::impl {
|
namespace wpi::util::impl {
|
||||||
@@ -32,10 +35,12 @@ SEMIWRAP_PYBIND11_MODULE(m) {
|
|||||||
cleanup_sendable_registry();
|
cleanup_sendable_registry();
|
||||||
cleanup_stack_trace_hook();
|
cleanup_stack_trace_hook();
|
||||||
cleanup_safethread_gil();
|
cleanup_safethread_gil();
|
||||||
|
cleanup_now_impl();
|
||||||
});
|
});
|
||||||
|
|
||||||
setup_safethread_gil();
|
setup_safethread_gil();
|
||||||
|
|
||||||
m.def("_setup_stack_trace_hook", &setup_stack_trace_hook);
|
m.def("_setup_stack_trace_hook", &setup_stack_trace_hook);
|
||||||
|
m.def("SetNowImpl", &set_now_impl);
|
||||||
m.add_object("_st_cleanup", cleanup);
|
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