Loading media/utils/include/mediautils/Runnable.h 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * 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 * * http://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. */ #pragma once #include <cstddef> #include <future> #include <memory> namespace android::mediautils { // Essentially std::function <void()>, but supports moveable types (and binds to any return type). // The lack of moveable is fixed in C++23, but we don't yet have it. // Also, SBO for std::packaged_task size, which is what we are using this for class Runnable { private: // src == nullptr => destroy the dest, otherwise move from src storage to dst, destroying src using move_destroy_fptr_t = void (*)(std::byte* dest, std::byte* src) noexcept; using call_fptr_t = void (*)(std::byte* storage); struct VTable { move_destroy_fptr_t move_destroy; call_fptr_t invoke; }; static void empty_move_destroy(std::byte*, std::byte*) noexcept {} static constexpr VTable empty_vtable{.move_destroy = empty_move_destroy, .invoke = nullptr}; template <typename T> static T& transmogrify(std::byte* addr) { return *std::launder(reinterpret_cast<T*>(addr)); } template <typename T> static void move_destroy_impl(std::byte* dest, std::byte* src) noexcept { if (src) { std::construct_at(&transmogrify<T>(dest), std::move(transmogrify<T>(src))); transmogrify<T>(src).~T(); } else { transmogrify<T>(dest).~T(); } } template <typename T> static void call_impl(std::byte* addr) { std::invoke(transmogrify<T>(addr)); } public: static constexpr size_t STORAGE_SIZE = sizeof(std::packaged_task<int()>); Runnable() = default; Runnable(std::nullptr_t) {} Runnable(const Runnable& o) = delete; Runnable(Runnable&& o) noexcept { // ask other vtable to move their storage into ours o.v.move_destroy(storage_, o.storage_); std::swap(v, o.v); } template <typename F> requires(std::is_invocable_v<std::decay_t<F>> && !std::is_same_v<std::decay_t<F>, Runnable> && std::is_move_constructible_v<std::decay_t<F>> && sizeof(std::decay_t<F>) <= STORAGE_SIZE) explicit Runnable(F&& task) : v{move_destroy_impl<std::decay_t<F>>, call_impl<std::decay_t<F>>} { std::construct_at(&transmogrify<std::decay_t<F>>(storage_), std::forward<F>(task)); } Runnable& operator=(const Runnable& o) = delete; Runnable& operator=(Runnable&& o) { // destroy ourselves v.move_destroy(storage_, nullptr); v = empty_vtable; // ask other vtable to move their storage into ours o.v.move_destroy(storage_, o.storage_); std::swap(v, o.v); return *this; } ~Runnable() { v.move_destroy(storage_, nullptr); } operator bool() const { return v.invoke != nullptr; } void operator()() { if (*this) v.invoke(storage_); } private: VTable v = empty_vtable; alignas(alignof(std::max_align_t)) std::byte storage_[STORAGE_SIZE]; }; } // namespace android::mediautils media/utils/tests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -285,5 +285,6 @@ cc_test { defaults: ["libmediautils_tests_defaults"], srcs: [ "jthread_tests.cpp", "runnable_tests.cpp", ], } media/utils/tests/runnable_tests.cpp 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * 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 * * http://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. */ #define LOG_TAG "runnable_tests" #include <mediautils/Runnable.h> #include <gtest/gtest.h> using namespace android::mediautils; struct Func { inline static int sMoveCtor = 0; inline static int sDtor = 0; // accumulator for call operator of this object inline static int sSum = 0; static constexpr int VAL1 = 7; static constexpr int VAL2 = 4; Func(int v) : value(v) {} Func(const Func&) = delete; Func(Func&& other) : value(other.value) { sMoveCtor++; } Func& operator=(const Func&) = delete; ~Func() { sDtor++; } void operator()() { sSum += value; } private: const int value; }; class RunnableTests : public ::testing::Test { protected: void SetUp() override { Func::sMoveCtor = 0; Func::sDtor = 0; Func::sSum = 0; } }; TEST_F(RunnableTests, testEmpty) { Runnable r1{}; Runnable r2{nullptr}; // empty func should do nothing, instead of crash r1(); r2(); EXPECT_FALSE(r1); EXPECT_FALSE(r2); } static int foo() { return 5; } struct Copy { Copy() {} Copy(const Copy&) {} Copy(Copy&&) {} void operator()(){} }; TEST_F(RunnableTests, testCompile) { const Copy b{}; Runnable r1{std::move(b)}; Runnable r2{b}; Runnable r4{foo}; std::unique_ptr<int> ptr; auto move_only = [ptr = std::move(ptr)](){}; Runnable r5{std::move(move_only)}; auto copyable = [](){}; Runnable r6{copyable}; } TEST_F(RunnableTests, testBool) { Runnable r1{[]() {}}; EXPECT_TRUE(r1); } TEST_F(RunnableTests, testCall) { Runnable r1{Func{Func::VAL1}}; EXPECT_TRUE(r1); r1(); EXPECT_EQ(Func::sSum, Func::VAL1); } TEST_F(RunnableTests, testDtor) { { Runnable r1{Func{Func::VAL1}}; } EXPECT_EQ(Func::sDtor, 2); } TEST_F(RunnableTests, testMoveCtor) { { Runnable moved_from{Func{Func::VAL1}}; EXPECT_EQ(Func::sMoveCtor, 1); EXPECT_EQ(Func::sDtor, 1); Runnable r1{std::move(moved_from)}; EXPECT_EQ(Func::sDtor, 2); // impl detail that we destroy internal obj after move EXPECT_EQ(Func::sMoveCtor, 2); EXPECT_TRUE(r1); EXPECT_FALSE(moved_from); r1(); EXPECT_EQ(Func::sSum, Func::VAL1); } EXPECT_EQ(Func::sDtor, 3); } TEST_F(RunnableTests, testMoveAssign) { { Runnable r1{Func{Func::VAL2}}; Runnable moved_from{Func{Func::VAL1}}; EXPECT_EQ(Func::sMoveCtor, 2); EXPECT_EQ(Func::sDtor, 2); r1(); EXPECT_EQ(Func::sSum, 4); r1 = std::move(moved_from); EXPECT_EQ(Func::sDtor, 4); // impl detail that we destroy internal obj after move EXPECT_EQ(Func::sMoveCtor, 3); EXPECT_TRUE(r1); EXPECT_FALSE(moved_from); r1(); // value should now hold Func::VAL1 EXPECT_EQ(Func::sSum, Func::VAL2 + Func::VAL1); } EXPECT_EQ(Func::sDtor, 5); } Loading
media/utils/include/mediautils/Runnable.h 0 → 100644 +111 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * 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 * * http://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. */ #pragma once #include <cstddef> #include <future> #include <memory> namespace android::mediautils { // Essentially std::function <void()>, but supports moveable types (and binds to any return type). // The lack of moveable is fixed in C++23, but we don't yet have it. // Also, SBO for std::packaged_task size, which is what we are using this for class Runnable { private: // src == nullptr => destroy the dest, otherwise move from src storage to dst, destroying src using move_destroy_fptr_t = void (*)(std::byte* dest, std::byte* src) noexcept; using call_fptr_t = void (*)(std::byte* storage); struct VTable { move_destroy_fptr_t move_destroy; call_fptr_t invoke; }; static void empty_move_destroy(std::byte*, std::byte*) noexcept {} static constexpr VTable empty_vtable{.move_destroy = empty_move_destroy, .invoke = nullptr}; template <typename T> static T& transmogrify(std::byte* addr) { return *std::launder(reinterpret_cast<T*>(addr)); } template <typename T> static void move_destroy_impl(std::byte* dest, std::byte* src) noexcept { if (src) { std::construct_at(&transmogrify<T>(dest), std::move(transmogrify<T>(src))); transmogrify<T>(src).~T(); } else { transmogrify<T>(dest).~T(); } } template <typename T> static void call_impl(std::byte* addr) { std::invoke(transmogrify<T>(addr)); } public: static constexpr size_t STORAGE_SIZE = sizeof(std::packaged_task<int()>); Runnable() = default; Runnable(std::nullptr_t) {} Runnable(const Runnable& o) = delete; Runnable(Runnable&& o) noexcept { // ask other vtable to move their storage into ours o.v.move_destroy(storage_, o.storage_); std::swap(v, o.v); } template <typename F> requires(std::is_invocable_v<std::decay_t<F>> && !std::is_same_v<std::decay_t<F>, Runnable> && std::is_move_constructible_v<std::decay_t<F>> && sizeof(std::decay_t<F>) <= STORAGE_SIZE) explicit Runnable(F&& task) : v{move_destroy_impl<std::decay_t<F>>, call_impl<std::decay_t<F>>} { std::construct_at(&transmogrify<std::decay_t<F>>(storage_), std::forward<F>(task)); } Runnable& operator=(const Runnable& o) = delete; Runnable& operator=(Runnable&& o) { // destroy ourselves v.move_destroy(storage_, nullptr); v = empty_vtable; // ask other vtable to move their storage into ours o.v.move_destroy(storage_, o.storage_); std::swap(v, o.v); return *this; } ~Runnable() { v.move_destroy(storage_, nullptr); } operator bool() const { return v.invoke != nullptr; } void operator()() { if (*this) v.invoke(storage_); } private: VTable v = empty_vtable; alignas(alignof(std::max_align_t)) std::byte storage_[STORAGE_SIZE]; }; } // namespace android::mediautils
media/utils/tests/Android.bp +1 −0 Original line number Diff line number Diff line Loading @@ -285,5 +285,6 @@ cc_test { defaults: ["libmediautils_tests_defaults"], srcs: [ "jthread_tests.cpp", "runnable_tests.cpp", ], }
media/utils/tests/runnable_tests.cpp 0 → 100644 +139 −0 Original line number Diff line number Diff line /* * Copyright (C) 2025 The Android Open Source Project * * 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 * * http://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. */ #define LOG_TAG "runnable_tests" #include <mediautils/Runnable.h> #include <gtest/gtest.h> using namespace android::mediautils; struct Func { inline static int sMoveCtor = 0; inline static int sDtor = 0; // accumulator for call operator of this object inline static int sSum = 0; static constexpr int VAL1 = 7; static constexpr int VAL2 = 4; Func(int v) : value(v) {} Func(const Func&) = delete; Func(Func&& other) : value(other.value) { sMoveCtor++; } Func& operator=(const Func&) = delete; ~Func() { sDtor++; } void operator()() { sSum += value; } private: const int value; }; class RunnableTests : public ::testing::Test { protected: void SetUp() override { Func::sMoveCtor = 0; Func::sDtor = 0; Func::sSum = 0; } }; TEST_F(RunnableTests, testEmpty) { Runnable r1{}; Runnable r2{nullptr}; // empty func should do nothing, instead of crash r1(); r2(); EXPECT_FALSE(r1); EXPECT_FALSE(r2); } static int foo() { return 5; } struct Copy { Copy() {} Copy(const Copy&) {} Copy(Copy&&) {} void operator()(){} }; TEST_F(RunnableTests, testCompile) { const Copy b{}; Runnable r1{std::move(b)}; Runnable r2{b}; Runnable r4{foo}; std::unique_ptr<int> ptr; auto move_only = [ptr = std::move(ptr)](){}; Runnable r5{std::move(move_only)}; auto copyable = [](){}; Runnable r6{copyable}; } TEST_F(RunnableTests, testBool) { Runnable r1{[]() {}}; EXPECT_TRUE(r1); } TEST_F(RunnableTests, testCall) { Runnable r1{Func{Func::VAL1}}; EXPECT_TRUE(r1); r1(); EXPECT_EQ(Func::sSum, Func::VAL1); } TEST_F(RunnableTests, testDtor) { { Runnable r1{Func{Func::VAL1}}; } EXPECT_EQ(Func::sDtor, 2); } TEST_F(RunnableTests, testMoveCtor) { { Runnable moved_from{Func{Func::VAL1}}; EXPECT_EQ(Func::sMoveCtor, 1); EXPECT_EQ(Func::sDtor, 1); Runnable r1{std::move(moved_from)}; EXPECT_EQ(Func::sDtor, 2); // impl detail that we destroy internal obj after move EXPECT_EQ(Func::sMoveCtor, 2); EXPECT_TRUE(r1); EXPECT_FALSE(moved_from); r1(); EXPECT_EQ(Func::sSum, Func::VAL1); } EXPECT_EQ(Func::sDtor, 3); } TEST_F(RunnableTests, testMoveAssign) { { Runnable r1{Func{Func::VAL2}}; Runnable moved_from{Func{Func::VAL1}}; EXPECT_EQ(Func::sMoveCtor, 2); EXPECT_EQ(Func::sDtor, 2); r1(); EXPECT_EQ(Func::sSum, 4); r1 = std::move(moved_from); EXPECT_EQ(Func::sDtor, 4); // impl detail that we destroy internal obj after move EXPECT_EQ(Func::sMoveCtor, 3); EXPECT_TRUE(r1); EXPECT_FALSE(moved_from); r1(); // value should now hold Func::VAL1 EXPECT_EQ(Func::sSum, Func::VAL2 + Func::VAL1); } EXPECT_EQ(Func::sDtor, 5); }