blob: 73ff4cb5a43eaf211ddb33cd5d68b127440001db [file]
// Copyright 2025 The Pigweed Authors
//
// Licensed under the Apache License, Version 2.0 (the "License"); you may not
// use this file except in compliance with the License. You may obtain a copy of
// the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
// WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
// License for the specific language governing permissions and limitations under
// the License.
#include "pw_assert/check.h"
#include "pw_async2/basic_dispatcher.h"
#include "pw_async2/future.h"
#include "pw_async2/pend_func_task.h"
#include "pw_digital_io/digital_io.h"
#include "pw_sync/interrupt_spin_lock.h"
#include "pw_unit_test/framework.h"
namespace {
// DOCSTAG: [pw_async2-examples-custom-future]
class ButtonReceiver;
class ButtonFuture
: public pw::async2::ListableFutureWithWaker<ButtonFuture, void> {
public:
// Provide a descriptive reason which can be used to debug blocked tasks.
static constexpr const char kWaitReason[] = "Waiting for button press";
// You are required to implement move semantics for your custom future,
// and to call `Base::MoveFrom` to ensure the intrusive list is updated.
ButtonFuture(ButtonFuture&& other) : Base(Base::kMovedFrom) {
Base::MoveFrom(other);
}
ButtonFuture& operator=(ButtonFuture&& other) {
Base::MoveFrom(other);
return *this;
}
private:
using Base = pw::async2::ListableFutureWithWaker<ButtonFuture, void>;
friend Base;
friend class ButtonReceiver;
explicit ButtonFuture(
pw::async2::SingleFutureProvider<ButtonFuture>& provider)
: Base(provider) {}
void HandlePress() {
pressed_ = true;
Base::Wake();
}
pw::async2::Poll<> DoPend(pw::async2::Context&) {
if (pressed_) {
return pw::async2::Ready();
}
return pw::async2::Pending();
}
bool pressed_ = false;
};
class ButtonReceiver {
public:
explicit ButtonReceiver(pw::digital_io::DigitalInterrupt& line)
: line_(line) {
PW_CHECK_OK(line_.SetInterruptHandler(
pw::digital_io::InterruptTrigger::kActivatingEdge,
[this](pw::digital_io::State) { HandleInterrupt(); }));
PW_CHECK_OK(line_.EnableInterruptHandler());
}
// Returns a future that completes when the button is pressed.
ButtonFuture WaitForPress() {
PW_ASSERT(!provider_.has_future());
return ButtonFuture(provider_);
}
private:
// Executed in interrupt context.
// `SingleFutureProvider` is internally synchronized and interrupt-safe.
void HandleInterrupt() {
if (auto future = provider_.Take()) {
future->get().HandlePress();
}
}
pw::digital_io::DigitalInterrupt& line_;
pw::async2::SingleFutureProvider<ButtonFuture> provider_;
};
// DOCSTAG: [pw_async2-examples-custom-future]
class DigitalInterruptMock : public pw::digital_io::DigitalInterrupt {
public:
DigitalInterruptMock() = default;
void Trigger() {
if (handler_) {
handler_(pw::digital_io::State::kActive);
}
}
private:
pw::Status DoEnable(bool) override { return pw::OkStatus(); }
pw::Status DoSetInterruptHandler(
pw::digital_io::InterruptTrigger,
pw::digital_io::InterruptHandler&& handler) override {
handler_ = std::move(handler);
return pw::OkStatus();
}
pw::Status DoEnableInterruptHandler(bool) override { return pw::OkStatus(); }
pw::digital_io::InterruptHandler handler_;
};
TEST(CustomFuture, CompilesAndRuns) {
pw::async2::BasicDispatcher dispatcher;
DigitalInterruptMock line;
ButtonReceiver receiver(line);
ButtonFuture future = receiver.WaitForPress();
pw::async2::PendFuncTask function_task([&future](pw::async2::Context& cx) {
return future.Pend(cx).Readiness();
});
dispatcher.Post(function_task);
dispatcher.RunUntilStalled();
// Simulate button press.
line.Trigger();
dispatcher.RunToCompletion();
}
} // namespace