blob: afc96e28b9fd9f06bb8a31e79190a5be66c603fd [file] [log] [blame]
#ifndef AOS_EVENTS_EVENT_LOOP_RUNTIME_H_
#define AOS_EVENTS_EVENT_LOOP_RUNTIME_H_
// Exposes the primitives to implement an async Rust runtime on top of an
// EventLoop. This is not intended to be used directly, so the APIs are not
// particularly ergonomic for C++. See the Rust wrapper for detailed
// documentation.
#include <chrono>
#include <memory>
#include <optional>
#include "aos/events/event_loop.h"
#include "aos/for_rust.h"
#include "cxx.h"
namespace aos {
// An alternative version of Context to feed autocxx, to work around
// https://github.com/google/autocxx/issues/787.
/// <div rustbindgen replaces="aos::Context"></div>
struct RustContext {
int64_t monotonic_event_time;
int64_t realtime_event_time;
int64_t monotonic_remote_time;
int64_t realtime_remote_time;
int64_t monotonic_remote_transmit_time;
uint32_t queue_index;
uint32_t remote_queue_index;
size_t size;
const void *data;
int buffer_index;
// Work around https://github.com/google/autocxx/issues/266.
uint8_t source_boot_uuid[16];
};
static_assert(sizeof(Context) == sizeof(RustContext));
static_assert(alignof(Context) == alignof(RustContext));
static_assert(offsetof(Context, monotonic_event_time) ==
offsetof(RustContext, monotonic_event_time));
static_assert(offsetof(Context, realtime_event_time) ==
offsetof(RustContext, realtime_event_time));
static_assert(offsetof(Context, monotonic_remote_time) ==
offsetof(RustContext, monotonic_remote_time));
static_assert(offsetof(Context, realtime_remote_time) ==
offsetof(RustContext, realtime_remote_time));
static_assert(offsetof(Context, monotonic_remote_transmit_time) ==
offsetof(RustContext, monotonic_remote_transmit_time));
static_assert(offsetof(Context, queue_index) ==
offsetof(RustContext, queue_index));
static_assert(offsetof(Context, remote_queue_index) ==
offsetof(RustContext, remote_queue_index));
static_assert(offsetof(Context, size) == offsetof(RustContext, size));
static_assert(offsetof(Context, data) == offsetof(RustContext, data));
static_assert(offsetof(Context, buffer_index) ==
offsetof(RustContext, buffer_index));
static_assert(offsetof(Context, source_boot_uuid) ==
offsetof(RustContext, source_boot_uuid));
static_assert(sizeof(Context::source_boot_uuid) ==
sizeof(RustContext::source_boot_uuid));
static_assert(sizeof(RustContext) == sizeof(Context),
"Update this when adding or removing fields");
// Similar to Rust's `Future<Output = Never>`.
class ApplicationFuture {
public:
ApplicationFuture() = default;
virtual ~ApplicationFuture() = default;
// Calls a Rust `Future::poll`, with a waker that will panic if used. Because
// our Future's Output is Never, the inner Rust implementation can only return
// Poll::Pending, which is equivalent to void.
//
// Returns true if it succeeded, or false if the Rust code paniced.
virtual bool Poll() = 0;
};
// Similar to Rust's `Stream<Item = const Option&>`.
class WatcherForRust {
public:
WatcherForRust(std::unique_ptr<RawFetcher> fetcher)
: fetcher_(std::move(fetcher)) {}
~WatcherForRust() = default;
const Context *PollNext() {
if (!fetcher_->FetchNext()) {
return nullptr;
}
return &fetcher_->context();
}
private:
const std::unique_ptr<RawFetcher> fetcher_;
};
class SenderForRust {
public:
SenderForRust(std::unique_ptr<RawSender> sender)
: sender_(std::move(sender)) {}
~SenderForRust() = default;
uint8_t *data() { return reinterpret_cast<uint8_t *>(sender_->data()); }
size_t size() { return sender_->size(); }
RawSender::Error SendBuffer(size_t size) { return sender_->Send(size); }
RawSender::Error CopyAndSend(const uint8_t *data, size_t size) {
return sender_->Send(data, size);
}
private:
const std::unique_ptr<RawSender> sender_;
};
class FetcherForRust {
public:
FetcherForRust(std::unique_ptr<RawFetcher> fetcher)
: fetcher_(std::move(fetcher)) {}
~FetcherForRust() = default;
bool FetchNext() { return fetcher_->FetchNext(); }
bool Fetch() { return fetcher_->Fetch(); }
const Context &context() const { return fetcher_->context(); }
private:
const std::unique_ptr<RawFetcher> fetcher_;
};
class EventLoopRuntime;
class OnRunForRust {
public:
OnRunForRust(const EventLoopRuntime *runtime);
~OnRunForRust();
bool is_running() const;
private:
const EventLoopRuntime *const runtime_;
};
class TimerForRust {
public:
static std::unique_ptr<TimerForRust> Make(const EventLoopRuntime *runtime);
TimerForRust(const TimerForRust &) = delete;
TimerForRust(TimerForRust &&) = delete;
TimerForRust &operator=(const TimerForRust &) = delete;
TimerForRust &operator=(TimerForRust &&) = delete;
~TimerForRust() { timer_->Disable(); }
void Schedule(int64_t base, int64_t repeat_offset) {
timer_->Schedule(
monotonic_clock::time_point(std::chrono::nanoseconds(base)),
std::chrono::nanoseconds(repeat_offset));
}
void Disable() { timer_->Disable(); }
bool IsDisabled() const { return timer_->IsDisabled(); }
void set_name(rust::Str name) { timer_->set_name(RustStrToStringView(name)); }
rust::Str name() const { return StringViewToRustStr(timer_->name()); }
// If true, the timer is expired.
bool Poll();
private:
TimerForRust() = default;
TimerHandler *timer_;
bool expired_ = false;
};
class EventLoopRuntime {
public:
EventLoopRuntime(const EventLoop *event_loop)
// SAFETY: A &EventLoop in Rust becomes a const EventLoop*. While
// that's generally a reasonable convention, they are semantically
// different. In Rust, a &mut EventLoop is very restrictive as it enforces
// uniqueness of the reference. Additionally, a &EventLoop doesn't convey
// const-ness in the C++ sense. So to make the FFI boundary more
// ergonomic, we allow a &EventLoop passed from rust to be translated into
// an EventLoop* in C++. This is safe so long as EventLoop is !Sync and no
// &mut EventLoop references are constructed in Rust.
: event_loop_(const_cast<EventLoop *>(event_loop)) {}
~EventLoopRuntime() {
// Do this first, because it may hold child objects.
task_.reset();
CHECK_EQ(child_count_, 0)
<< ": Some child objects were not destroyed first";
}
EventLoop *event_loop() const { return event_loop_; }
void Spawn(std::unique_ptr<ApplicationFuture> task) const {
CHECK(!task_) << ": May only call Spawn once";
task_ = std::move(task);
DoPoll();
// Just do this unconditionally, so we don't have to keep track of each
// OnRun to only do it once. If Rust doesn't use OnRun, it's harmless to do
// an extra poll.
event_loop_->OnRun([this] { DoPoll(); });
}
const Configuration *configuration() const {
return event_loop_->configuration();
}
const Node *node() const { return event_loop_->node(); }
bool is_running() const { return event_loop_->is_running(); }
// autocxx generates broken C++ code for `time_point`, see
// https://github.com/google/autocxx/issues/787.
int64_t monotonic_now() const {
return std::chrono::nanoseconds(
event_loop_->monotonic_now().time_since_epoch())
.count();
}
int64_t realtime_now() const {
return std::chrono::nanoseconds(
event_loop_->realtime_now().time_since_epoch())
.count();
}
rust::Str name() const { return StringViewToRustStr(event_loop_->name()); }
WatcherForRust MakeWatcher(const Channel *channel) const {
event_loop_->MakeRawNoArgWatcher(channel,
[this](const Context &) { DoPoll(); });
return WatcherForRust(event_loop_->MakeRawFetcher(channel));
}
SenderForRust MakeSender(const Channel *channel) const {
return SenderForRust(event_loop_->MakeRawSender(channel));
}
FetcherForRust MakeFetcher(const Channel *channel) const {
return FetcherForRust(event_loop_->MakeRawFetcher(channel));
}
OnRunForRust MakeOnRun() const { return OnRunForRust(this); }
std::unique_ptr<TimerForRust> AddTimer() const {
return TimerForRust::Make(this);
}
void SetRuntimeRealtimePriority(int priority) const {
event_loop_->SetRuntimeRealtimePriority(priority);
}
void SetRuntimeAffinity(const cpu_set_t &cpuset) const {
event_loop_->SetRuntimeAffinity(cpuset);
}
private:
friend class OnRunForRust;
friend class TimerForRust;
// Polls the top-level future once. This is what all the callbacks should do.
void DoPoll() const {
if (task_) {
CHECK(task_->Poll()) << ": Rust panic, aborting";
}
}
EventLoop *const event_loop_;
// For Rust's EventLoopRuntime to be semantically equivelant to C++'s event
// loop, we need the ability to have shared references (&EventLoopRuntime) on
// the Rust side. Without that, the API would be overly restrictive to be
// usable. In order for the generated code to use &self references on methods,
// they need to be marked `const` on the C++ side. We use the `mutable`
// keyword to allow mutation through `const` methods.
//
// SAFETY:
// * The event loop runtime must be `!Sync` in the Rust side (default).
// * We can't expose exclusive references (&mut) to either of the mutable
// fields on the Rust side from a shared reference (&).
mutable std::unique_ptr<ApplicationFuture> task_;
mutable int child_count_ = 0;
};
} // namespace aos
#endif // AOS_EVENTS_EVENT_LOOP_RUNTIME_H_