Skip to content

Commit

Permalink
Eliminated heap.
Browse files Browse the repository at this point in the history
  • Loading branch information
serges147 committed Aug 23, 2024
1 parent b7cf105 commit 3a6cd71
Show file tree
Hide file tree
Showing 4 changed files with 148 additions and 272 deletions.
1 change: 0 additions & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,5 @@

- [ ] Search for...
- [ ] "serge"
- [ ] "embedded" -> "olg"

- [ ] Replace "serges147" with proper organization name when ready
86 changes: 42 additions & 44 deletions include/olg_scheduler/scheduler.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,12 +18,13 @@
#pragma once

#include <cavl/cavl.hpp>
#include <platform/heap.hpp>

#include <cassert>
#include <chrono>
#include <concepts>
#include <functional>
#include <optional>
#include <utility>
#include <variant>

namespace olg_scheduler
Expand All @@ -48,10 +49,10 @@ struct Arg final
template <typename Fun, typename TimePoint>
concept Callback = std::invocable<Fun, const Arg<TimePoint>&>;

/// Events are created by the event loop on the heap using the factory methods and returned by pointer.
/// Events are created on the stack.
/// The caller can destroy the event at any time, which will automatically cancel and remove it from the event loop.
/// If the event is single-shot or is otherwise canceled, the event object will keep lingering on the heap
/// until destroyed by the user.
/// If the event is single-shot or is otherwise canceled, the event object will keep lingering on the stack
/// until destroyed by the user (f.e. by `reset`-ing an optional which holds it).
///
/// The events are guaranteed to be executed strictly in the order of their deadlines;
/// equal deadlines are resolved strictly in the order of their registration (first registered -- first executed).
Expand All @@ -62,18 +63,17 @@ concept Callback = std::invocable<Fun, const Arg<TimePoint>&>;
/// The time complexity of all operations is logarithmic of the number of registered events.
///
/// Events shall not outlive the event loop.
/// Event handles shall not be destroyed from callbacks.
/// Events could be cancelled or destroyed from callbacks (even for themselves).
template <typename TimePoint>
class Event : private cavl::Node<Event<TimePoint>>
{
friend class cavl::Node<Event>; // This friendship is required for the AVL tree implementation,
friend class cavl::Tree<Event>; // otherwise, we would have to use public inheritance, which is undesirable.

public:
Event(const Event&) = delete;
Event(Event&&) = delete;
Event& operator=(const Event&) = delete;
Event& operator=(Event&&) = delete;
Event(const Event&) = delete;
Event& operator=(const Event&) = delete;
Event& operator=(Event&& other) noexcept = delete;

/// The event can be safely destroyed at any time.
/// It will be automatically canceled and removed from the event loop.
Expand All @@ -96,7 +96,7 @@ class Event : private cavl::Node<Event<TimePoint>>
{
// Removing a non-existent node from the tree is an UB that may lead to memory corruption,
// so we have to check first if the event is actually registered.
tree_.remove(this);
tree_.get().remove(this);
// This is used to mark the event as unregistered so we don't double-remove it.
// This can only be done after the event is removed from the tree.
deadline_.reset();
Expand All @@ -110,7 +110,13 @@ class Event : private cavl::Node<Event<TimePoint>>

protected:
/// The event is not automatically scheduled, it must be explicitly scheduled by the user.
explicit Event(cavl::Tree<Event>& tree) : tree_(tree) {}
explicit Event(cavl::Tree<Event>& tree) : tree_{tree} {}

Event(Event&& other) noexcept :
cavl::Node<Event>{std::move(static_cast<cavl::Node<Event>&>(other))},
tree_{other.tree_},
deadline_{std::exchange(other.deadline_, std::nullopt)}
{}

/// Ensure the event is in the tree and set the deadline to the specified absolute time point.
/// If the event is already scheduled, the old deadline is overwritten.
Expand All @@ -119,7 +125,7 @@ class Event : private cavl::Node<Event<TimePoint>>
{
cancel();
deadline_ = dead; // The deadline shall be set before the event is inserted into the tree.
const auto ptr_existing = tree_.search(
const auto ptr_existing = tree_.get().search(
[dead](const Event& other) {
/// No two deadlines compare equal, which allows us to have multiple nodes with the same deadline in
/// the tree. With two nodes sharing the same deadline, the one added later is considered to be later.
Expand All @@ -138,14 +144,10 @@ class Event : private cavl::Node<Event<TimePoint>>
virtual void execute(const Arg<TimePoint>& args) = 0;

private:
cavl::Tree<Event>& tree_;
std::optional<TimePoint> deadline_;
std::reference_wrapper<cavl::Tree<Event>> tree_;
std::optional<TimePoint> deadline_;
};

/// A helper alias for convenience.
template <typename TimePoint>
using EventPtr = platform::heap::UniquePtr<Event<TimePoint>>;

/// This information is returned by the spin() method to allow the caller to decide what to do next
/// and assess the temporal performance of the event loop.
template <typename Clock>
Expand Down Expand Up @@ -186,7 +188,7 @@ class EventLoop final
class EventProxy : public Event<time_point>
{
public:
explicit EventProxy(EventLoop& owner) : Event<time_point>(owner.tree_) {}
explicit EventProxy(EventLoop& owner) : Event<time_point>{owner.tree_} {}
using Event<time_point>::execute;
};

Expand All @@ -208,30 +210,29 @@ class EventLoop final
/// if you also need to invoke it immediately, consider using defer().
/// Returns the event handle on success, empty if out of memory or invalid inputs.
template <Callback<time_point> Fun>
[[nodiscard]] EventPtr<time_point> repeat(const duration period, Fun&& handler)
[[nodiscard]] auto repeat(const duration period, Fun&& handler)
{
class Impl final : public EventProxy
{
public:
Impl(EventLoop& owner, const duration per, const Fun& fun) : EventProxy(owner), period_(per), handler_(fun)
Impl(EventLoop& owner, const duration per, Fun&& fun) :
EventProxy{owner}, period_{per}, handler_{std::move(fun)}
{
this->schedule(Clock::now() + period_);
}
void execute(const Arg<time_point>& args) final

void execute(const Arg<time_point>& args) override
{
this->schedule(args.deadline + period_); // Strict period advancement, no phase error growth.
handler_(args);
}

private:
const duration period_;
Fun handler_;
duration period_;
Fun handler_;
};
if (period > duration::zero())
{
return platform::heap::construct<Impl>(*this, period, std::forward<Fun>(handler));
}
return {};
assert(period > duration::zero());
return Impl{*this, period, std::forward<Fun>(handler)};
}

/// This is like repeat() with one crucial difference: the next deadline is defined not as (deadline+period),
Expand All @@ -241,46 +242,43 @@ class EventLoop final
/// like interface polling or similar. Because the scheduler is allowed to let the phase slip, this type of event
/// will automatically reduce the activation rate if the scheduler is CPU-starved, thus providing load regulation.
template <Callback<time_point> Fun>
[[nodiscard]] EventPtr<time_point> poll(const duration min_period, Fun&& handler)
[[nodiscard]] auto poll(const duration min_period, Fun&& handler)
{
class Impl final : public EventProxy
{
public:
Impl(EventLoop& owner, const duration per, const Fun& fun) :
EventProxy(owner), min_period_(per), handler_(fun)
Impl(EventLoop& owner, const duration per, Fun&& fun) :
EventProxy{owner}, min_period_{per}, handler_{std::move(fun)}
{
this->schedule(Clock::now() + min_period_);
}
void execute(const Arg<time_point>& args) final
void execute(const Arg<time_point>& args) override
{
this->schedule(args.approx_now + min_period_); // Accumulate phase error intentionally.
handler_(args);
}

private:
const duration min_period_;
Fun handler_;
duration min_period_;
Fun handler_;
};
if (min_period > duration::zero())
{
return platform::heap::construct<Impl>(*this, min_period, std::forward<Fun>(handler));
}
return {};
assert(min_period > duration::zero());
return Impl{*this, min_period, std::forward<Fun>(handler)};
}

/// Like repeat() but the handler will be invoked only once and the event is canceled afterward.
/// The deadline may be in the past, in which case the event will fire ASAP.
template <Callback<time_point> Fun>
[[nodiscard]] EventPtr<time_point> defer(const time_point deadline, Fun&& handler)
[[nodiscard]] auto defer(const time_point deadline, Fun&& handler)
{
class Impl final : public EventProxy
{
public:
Impl(EventLoop& owner, const time_point deadline, const Fun& fun) : EventProxy(owner), handler_(fun)
Impl(EventLoop& owner, const time_point deadline, Fun&& fun) : EventProxy{owner}, handler_{std::move(fun)}
{
this->schedule(deadline);
}
void execute(const Arg<time_point>& args) final
void execute(const Arg<time_point>& args) override
{
this->cancel();
handler_(args);
Expand All @@ -289,7 +287,7 @@ class EventLoop final
private:
Fun handler_;
};
return platform::heap::construct<Impl>(*this, deadline, std::forward<Fun>(handler));
return Impl{*this, deadline, std::forward<Fun>(handler)};
}

/// Execute pending events strictly in the order of their deadlines until there are no pending events left.
Expand Down
101 changes: 0 additions & 101 deletions lib/platform/heap.hpp

This file was deleted.

Loading

0 comments on commit 3a6cd71

Please sign in to comment.