1 /* 2 * Copyright (C) 2025 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 #pragma once 18 19 #include <cstddef> 20 #include <future> 21 #include <memory> 22 23 namespace android::mediautils { 24 25 // Essentially std::function <void()>, but supports moveable types (and binds to any return type). 26 // The lack of moveable is fixed in C++23, but we don't yet have it. 27 // Also, SBO for std::packaged_task size, which is what we are using this for 28 class Runnable { 29 private: 30 // src == nullptr => destroy the dest, otherwise move from src storage to dst, destroying src 31 using move_destroy_fptr_t = void (*)(std::byte* dest, std::byte* src) noexcept; 32 using call_fptr_t = void (*)(std::byte* storage); 33 34 struct VTable { 35 move_destroy_fptr_t move_destroy; 36 call_fptr_t invoke; 37 }; 38 empty_move_destroy(std::byte *,std::byte *)39 static void empty_move_destroy(std::byte*, std::byte*) noexcept {} 40 static constexpr VTable empty_vtable{.move_destroy = empty_move_destroy, .invoke = nullptr}; 41 42 template <typename T> transmogrify(std::byte * addr)43 static T& transmogrify(std::byte* addr) { 44 return *std::launder(reinterpret_cast<T*>(addr)); 45 } 46 47 template <typename T> move_destroy_impl(std::byte * dest,std::byte * src)48 static void move_destroy_impl(std::byte* dest, std::byte* src) noexcept { 49 if (src) { 50 std::construct_at(&transmogrify<T>(dest), std::move(transmogrify<T>(src))); 51 transmogrify<T>(src).~T(); 52 } else { 53 transmogrify<T>(dest).~T(); 54 } 55 } 56 57 template <typename T> call_impl(std::byte * addr)58 static void call_impl(std::byte* addr) { 59 std::invoke(transmogrify<T>(addr)); 60 } 61 62 public: 63 static constexpr size_t STORAGE_SIZE = sizeof(std::packaged_task<int()>); 64 65 Runnable() = default; 66 Runnable(std::nullptr_t)67 Runnable(std::nullptr_t) {} 68 69 Runnable(const Runnable& o) = delete; 70 Runnable(Runnable && o)71 Runnable(Runnable&& o) noexcept { 72 // ask other vtable to move their storage into ours 73 o.v.move_destroy(storage_, o.storage_); 74 std::swap(v, o.v); 75 } 76 77 template <typename F> 78 requires(std::is_invocable_v<std::decay_t<F>> && 79 !std::is_same_v<std::decay_t<F>, Runnable> && 80 std::is_move_constructible_v<std::decay_t<F>> && 81 sizeof(std::decay_t<F>) <= STORAGE_SIZE) Runnable(F && task)82 explicit Runnable(F&& task) 83 : v{move_destroy_impl<std::decay_t<F>>, call_impl<std::decay_t<F>>} { 84 std::construct_at(&transmogrify<std::decay_t<F>>(storage_), std::forward<F>(task)); 85 } 86 87 Runnable& operator=(const Runnable& o) = delete; 88 89 Runnable& operator=(Runnable&& o) { 90 // destroy ourselves 91 v.move_destroy(storage_, nullptr); 92 v = empty_vtable; 93 // ask other vtable to move their storage into ours 94 o.v.move_destroy(storage_, o.storage_); 95 std::swap(v, o.v); 96 return *this; 97 } 98 ~Runnable()99 ~Runnable() { v.move_destroy(storage_, nullptr); } 100 101 operator bool() const { return v.invoke != nullptr; } 102 operator()103 void operator()() { 104 if (*this) v.invoke(storage_); 105 } 106 107 private: 108 VTable v = empty_vtable; 109 alignas(alignof(std::max_align_t)) std::byte storage_[STORAGE_SIZE]; 110 }; 111 } // namespace android::mediautils 112