Files
cardboy/Firmware/sdk/backend_interface/include/cardboy/sdk/platform.hpp
2025-10-11 20:03:00 +02:00

135 lines
3.7 KiB
C++

#pragma once
#include "input_state.hpp"
#include <concepts>
#include <cstdint>
namespace cardboy::sdk {
namespace detail {
template<typename Impl>
concept HasClearImpl = requires(Impl& impl, bool value) {
{ impl.clear_impl(value) };
};
template<typename Impl>
concept HasFrameReadyImpl = requires(Impl& impl) {
{ impl.frameReady_impl() };
};
template<typename Impl>
concept HasSendFrameImpl = requires(Impl& impl, bool flag) {
{ impl.sendFrame_impl(flag) };
};
template<typename Impl>
concept HasDrawBits8Impl = requires(Impl& impl, int x, int y, std::uint8_t bits) { impl.drawBits8_impl(x, y, bits); };
template<typename Impl>
concept HasFrameInFlightImpl = requires(const Impl& impl) {
{ impl.frameInFlight_impl() } -> std::convertible_to<bool>;
};
template<typename Impl>
concept HasSleepMsImpl = requires(Impl& impl, std::uint32_t value) {
{ impl.sleep_ms_impl(value) };
};
} // namespace detail
template<typename Impl>
class FramebufferFacade {
public:
[[nodiscard]] __attribute__((always_inline)) int width() const { return impl().width_impl(); }
[[nodiscard]] __attribute__((always_inline)) int height() const { return impl().height_impl(); }
__attribute__((always_inline)) void drawPixel(int x, int y, bool on) { impl().drawPixel_impl(x, y, on); }
__attribute__((always_inline)) void drawBits8(int x, int y, std::uint8_t bits) {
if constexpr (detail::HasDrawBits8Impl<Impl>) {
impl().drawBits8_impl(x, y, bits);
} else {
defaultDrawBits8(x, y, bits);
}
}
void clear(bool on) {
if constexpr (detail::HasClearImpl<Impl>) {
impl().clear_impl(on);
} else {
defaultClear(on);
}
}
__attribute__((always_inline)) void frameReady() {
if constexpr (detail::HasFrameReadyImpl<Impl>)
impl().frameReady_impl();
}
__attribute__((always_inline)) void sendFrame(bool clearDrawBuffer = true) {
if constexpr (detail::HasSendFrameImpl<Impl>)
impl().sendFrame_impl(clearDrawBuffer);
}
[[nodiscard]] __attribute__((always_inline)) bool isFrameInFlight() const {
if constexpr (detail::HasFrameInFlightImpl<Impl>)
return impl().frameInFlight_impl();
return false;
}
protected:
FramebufferFacade() = default;
~FramebufferFacade() = default;
private:
[[nodiscard]] __attribute__((always_inline)) Impl& impl() { return static_cast<Impl&>(*this); }
[[nodiscard]] __attribute__((always_inline)) const Impl& impl() const { return static_cast<const Impl&>(*this); }
void defaultClear(bool on) {
for (int y = 0; y < height(); ++y)
for (int x = 0; x < width(); ++x)
drawPixel(x, y, on);
}
void defaultDrawBits8(int x, int y, std::uint8_t bits) {
for (int col = 0; col < 8; ++col) {
const std::uint8_t mask = static_cast<std::uint8_t>(1u << (7 - col));
const bool pixelOn = (bits & mask) != 0;
drawPixel(x + col, y, pixelOn);
}
}
};
template<typename Impl>
class InputFacade {
public:
InputState readState() { return impl().readState_impl(); }
protected:
InputFacade() = default;
~InputFacade() = default;
private:
[[nodiscard]] Impl& impl() { return static_cast<Impl&>(*this); }
};
template<typename Impl>
class ClockFacade {
public:
std::uint32_t millis() { return impl().millis_impl(); }
void sleep_ms(std::uint32_t ms) {
if constexpr (detail::HasSleepMsImpl<Impl>)
impl().sleep_ms_impl(ms);
}
protected:
ClockFacade() = default;
~ClockFacade() = default;
private:
[[nodiscard]] Impl& impl() { return static_cast<Impl&>(*this); }
};
} // namespace cardboy::sdk