Skip to content
Closed
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 1 addition & 2 deletions MANIFEST.in
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
include README.md pyproject.toml setup.py SConstruct SConscript
recursive-include site_scons *.py
recursive-include msgq *.py *.pyx *.pxd *.h *.cc
recursive-include msgq *.py *.h *.cc
global-exclude *.so *.o *.os *.a *.pyc
9 changes: 4 additions & 5 deletions SConscript
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
Import('env', 'envCython', 'common')
Import('env', 'envPython', 'nanobind_lib', 'common')


visionipc_dir = Dir('msgq/visionipc')
Expand All @@ -10,7 +10,7 @@ msgq_objects = env.SharedObject([
'msgq/msgq.cc',
])
msgq = env.Library('msgq', msgq_objects)
msgq_python = envCython.Program('msgq/ipc_pyx.so', 'msgq/ipc_pyx.pyx', LIBS=envCython["LIBS"]+[msgq]+common)
msgq_python = envPython.Program('msgq/ipc_pyx.so', 'msgq/ipc_module.cc', LIBS=[msgq, nanobind_lib]+common)

# Build Vision IPC
vipc_files = ['visionipc.cc', 'visionipc_server.cc', 'visionipc_client.cc']
Expand All @@ -24,9 +24,8 @@ vipc_objects = env.SharedObject(vipc_sources)
visionipc = env.Library('visionipc', vipc_objects)


vipc_libs = envCython["LIBS"] + [visionipc, msgq] + common
envCython.Program(f'{visionipc_dir.abspath}/visionipc_pyx.so', f'{visionipc_dir.abspath}/visionipc_pyx.pyx',
LIBS=vipc_libs)
envPython.Program(f'{visionipc_dir.abspath}/visionipc_pyx.so', f'{visionipc_dir.abspath}/visionipc_module.cc',
LIBS=[visionipc, msgq, nanobind_lib]+common)

if GetOption('extras'):
env.Program('msgq/test_runner', ['msgq/msgq_tests.cc'], LIBS=[msgq]+common)
Expand Down
27 changes: 19 additions & 8 deletions SConstruct
Original file line number Diff line number Diff line change
@@ -1,8 +1,11 @@
import os
import platform
import subprocess
import sys
import sysconfig

import nanobind

arch = subprocess.check_output(["uname", "-m"], encoding='utf8').rstrip()
if platform.system() == "Darwin":
arch = "Darwin"
Expand Down Expand Up @@ -62,21 +65,29 @@ env = Environment(
CFLAGS="-std=gnu11",
CXXFLAGS="-std=c++1z",
CPPPATH=cpppath,
CYTHONCFILESUFFIX=".cpp",
tools=["default", "cython"]
)

Export('env', 'arch', 'common')

envCython = env.Clone(LIBS=[])
envCython["CCFLAGS"] += ["-Wno-#warnings", "-Wno-cpp", "-Wno-shadow", "-Wno-deprecated-declarations"]
envCython["CCFLAGS"].remove('-Werror')
envPython = env.Clone(LIBS=[])
envPython["CCFLAGS"] += ["-fvisibility=hidden", "-isystem", nanobind.include_dir()]
envPython["CPPDEFINES"] = ["NB_COMPACT_ASSERTIONS"]
# On Python >= 3.12, build against the limited API so one build works on every newer Python
if sys.version_info >= (3, 12):
envPython["CPPDEFINES"] += [("Py_LIMITED_API", "0x030C0000")]
if arch == "Darwin":
envCython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"]
envPython["LINKFLAGS"] = ["-bundle", "-undefined", "dynamic_lookup"]
else:
envCython["LINKFLAGS"] = ["-pthread", "-shared"]
envPython["LINKFLAGS"] = ["-pthread", "-shared"]

# nanobind's runtime, shared by both extension modules
envNanobind = envPython.Clone()
envNanobind["CCFLAGS"] = [f for f in envNanobind["CCFLAGS"] if f != "-Werror" and not f.startswith("-Wshadow")]
envNanobind["CCFLAGS"] += ["-fno-strict-aliasing", "-isystem", f"{nanobind.source_dir()}/../ext/robin_map/include"]
envNanobind["CPPDEFINES"] += ["NB_BUILD"]
nanobind_lib = envNanobind.Library('nanobind', envNanobind.SharedObject('nanobind', f"{nanobind.source_dir()}/nb_combined.cpp"))

Export('envCython')
Export('envPython', 'nanobind_lib')


SConscript(['SConscript'])
4 changes: 2 additions & 2 deletions lefthook.yml
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,12 @@ test:
ty:
run: ty check .
codespell:
run: codespell {files} -L ned,stdio,master --builtin clear,rare,informal,usage,code,names,en-GB_to_en-US -S uv.lock,*_pyx.cpp
run: codespell {files} -L ned,stdio,master --builtin clear,rare,informal,usage,code,names,en-GB_to_en-US -S uv.lock
files: git ls-tree -r HEAD --name-only
cppcheck:
run: cppcheck --error-exitcode=1 --inline-suppr --language=c++ --force --quiet -j4 --check-level=exhaustive $(git ls-files '*.cc' | grep -v -E '(msgq_tests|test_runner)\.cc')
cpplint:
run: cpplint --exclude=msgq/ipc_pyx.cpp --exclude=msgq/visionipc/visionipc_pyx.cpp --recursive --quiet --counting=detailed --linelength=240 --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces msgq/
run: cpplint --recursive --quiet --counting=detailed --linelength=240 --filter=-build,-legal,-readability,-runtime,-whitespace,+build/include_subdir,+build/forward_decl,+build/include_what_you_use,+build/deprecated,+whitespace/comma,+whitespace/line_length,+whitespace/empty_if_body,+whitespace/empty_loop_body,+whitespace/empty_conditional_body,+whitespace/forcolon,+whitespace/parens,+whitespace/semicolon,+whitespace/tab,+readability/braces msgq/

# *** tests ***
test_runner:
Expand Down
1 change: 0 additions & 1 deletion msgq/.gitignore

This file was deleted.

61 changes: 0 additions & 61 deletions msgq/ipc.pxd

This file was deleted.

155 changes: 155 additions & 0 deletions msgq/ipc_module.cc
Original file line number Diff line number Diff line change
@@ -0,0 +1,155 @@
// Python bindings for msgq
#include <cerrno>
#include <chrono>
#include <cstring>
#include <stdexcept>
#include <string>
#include <thread>
#include <vector>

#include <nanobind/nanobind.h>
#include <nanobind/stl/string.h>
#include <nanobind/stl/vector.h>

#include "msgq/ipc.h"
#include "msgq/event.h"
#include "msgq/nb_helpers.h"

using namespace nb::literals;

struct IpcError : std::runtime_error {
using std::runtime_error::runtime_error;
};
struct MultiplePublishersError : IpcError {
using IpcError::IpcError;
};

[[noreturn]] static void throw_ipc_error(const std::string *endpoint = nullptr) {
int err = errno;
std::string msg = "Messaging failure " + (endpoint ? "with " + *endpoint : "") + ": " + strerror(err);
if (err == EADDRINUSE) throw MultiplePublishersError(msg);
throw IpcError(msg);
}

// Keeps the registered Python SubSockets alive, and lets poll() return them
struct PyPoller {
Poller poller;
std::vector<nb::object> sockets;
};

NB_MODULE(ipc_pyx, m) {
auto ipc_error = nb::exception<IpcError>(m, "IpcError");
nb::exception<MultiplePublishersError>(m, "MultiplePublishersError", ipc_error);

nb::class_<Event>(m, "Event")
.def(nb::init<>())
.def("set", &Event::set)
.def("clear", &Event::clear)
.def("wait", &Event::wait, "timeout"_a = -1, nb::call_guard<nb::gil_scoped_release>())
.def("peek", &Event::peek)
.def_prop_ro("fd", &Event::fd)
.def_prop_ro("ptr", [](Event &self) { return (uintptr_t)&self; });

nb::class_<SocketEventHandle>(m, "SocketEventHandle")
.def("__init__", [](SocketEventHandle *self, Name endpoint, Name identifier, bool override) {
new (self) SocketEventHandle(endpoint.value, identifier.value, override);
}, "endpoint"_a, "identifier"_a, "override"_a)
.def_prop_rw("enabled", &SocketEventHandle::is_enabled, &SocketEventHandle::set_enabled)
.def_prop_ro("recv_called_event", &SocketEventHandle::recv_called)
.def_prop_ro("recv_ready_event", &SocketEventHandle::recv_ready);

m.def("toggle_fake_events", &SocketEventHandle::toggle_fake_events, "enabled"_a);
m.def("set_fake_prefix", [](Name prefix) { SocketEventHandle::set_fake_prefix(prefix.value); }, "prefix"_a);
m.def("get_fake_prefix", [] {
std::string prefix = SocketEventHandle::fake_prefix();
return nb::bytes(prefix.data(), prefix.size());
});
m.def("delete_fake_prefix", [] { SocketEventHandle::set_fake_prefix(""); });
m.def("wait_for_one_event", &Event::wait_for_one, "events"_a, "timeout"_a = -1, nb::call_guard<nb::gil_scoped_release>());

nb::class_<SubSocket>(m, "SubSocket")
.def(nb::init<>())
.def("connect", [](SubSocket &self, Name endpoint, bool conflate, size_t segment_size) {
int r;
{
nb::gil_scoped_release release;
r = self.connect(endpoint.value, conflate, segment_size);
}
if (r != 0) throw_ipc_error(&endpoint.value);
// cppcheck-suppress assignBoolToPointer
}, "endpoint"_a, "conflate"_a = false, "segment_size"_a = 0)
.def("setTimeout", &SubSocket::setTimeout, "timeout"_a, nb::call_guard<nb::gil_scoped_release>())
.def("receive", [](SubSocket &self, bool non_blocking) -> nb::object {
Message *msg;
if (non_blocking) {
// a non-blocking receive is too fast to be worth releasing the GIL
msg = self.receive(true);
} else {
nb::gil_scoped_release release;
msg = self.receive(false);
}
if (!msg) return nb::none();
nb::bytes ret(msg->getData(), msg->getSize());
delete msg;
return ret;
// cppcheck-suppress assignBoolToPointer
}, "non_blocking"_a = false);

nb::class_<PubSocket>(m, "PubSocket")
.def(nb::init<>())
.def("connect", [](PubSocket &self, Name endpoint, size_t segment_size) {
if (self.connect(endpoint.value, segment_size) != 0) throw_ipc_error(&endpoint.value);
}, "endpoint"_a, "segment_size"_a = 0)
.def("send", [](PubSocket &self, nb::handle data) {
int r;
size_t len;
if (nb::isinstance<nb::bytes>(data)) {
auto bytes = nb::borrow<nb::bytes>(data);
len = bytes.size();
r = self.send((char *)bytes.c_str(), len);
} else {
Buffer buf(data);
len = buf.size();
r = self.send(buf.data(), len);
}
if (r != (int)len) throw_ipc_error();
}, "data"_a)
.def("all_readers_updated", &PubSocket::all_readers_updated)
.def("wait_for_readers", [](PubSocket &self, double timeout, double interval) {
auto deadline = std::chrono::steady_clock::now() + std::chrono::duration<double>(timeout);
while (std::chrono::steady_clock::now() < deadline) {
if (self.all_readers_updated()) return;
{
nb::gil_scoped_release release;
std::this_thread::sleep_for(std::chrono::duration<double>(interval));
}
if (PyErr_CheckSignals() < 0) throw nb::python_error();
}
PyErr_SetString(PyExc_TimeoutError, "subscriber did not connect");
throw nb::python_error();
}, "timeout"_a = 1.0, "interval"_a = 0.001);

nb::class_<PyPoller>(m, "Poller")
.def(nb::init<>())
.def("registerSocket", [](PyPoller &self, nb::object socket) {
self.poller.registerSocket(nb::cast<SubSocket *>(socket));
self.sockets.push_back(socket);
}, "socket"_a)
.def("poll", [](PyPoller &self, int timeout) {
std::vector<SubSocket *> ready;
{
nb::gil_scoped_release release;
ready = self.poller.poll(timeout);
}
nb::list ret;
for (SubSocket *s : ready) {
for (const nb::object &socket : self.sockets) {
if (nb::inst_ptr<SubSocket>(socket) == s) {
ret.append(socket);
break;
}
}
}
return ret;
}, "timeout"_a);
}
Loading
Loading