// Copyright 2021 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. // Copyright 2017 The Fuchsia Authors. All rights reserved. // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. #pragma once #include "FunctionInternal.h" #include "UtilityInternal.h" namespace gfxstream::guest { namespace fit { template class FunctionImpl; template class CallbackImpl; // The default size allowance for storing a target inline within a function // object, in bytes. This default allows for inline storage of targets // as big as two pointers, such as an object pointer and a pointer to a member // function. constexpr size_t kDefaultInlineTargetSize = sizeof(void*) * 2; // A |fit::Function| is a move-only polymorphic function wrapper. // // If you need a class with similar characteristics that also ensures // "run-once" semantics (such as callbacks shared with timeouts, or for // service requests with redundant, failover, or fallback service providers), // see |fit::Callback|. // // |fit::Function| behaves like |std::function| except that it is // move-only instead of copyable, so it can hold targets that cannot be copied, // such as mutable lambdas, and immutable lambdas that capture move-only // objects. // // Targets of up to |inlineTargetSize| bytes in size (rounded up for memory // alignment) are stored inline within the function object without incurring // any heap allocation. Larger callable objects will be moved to the heap as // required. // // See also |fit::InlineFunction| for more control over allocation // behavior. // // SYNOPSIS // // |T| is the function's signature. e.g. void(int, std::string). // // |inlineTargetSize| is the minimum size of target that is guaranteed to // fit within a function without requiring heap allocation. // Defaults to |kDefaultInlineTargetSize|. // // Class members are documented in |fit::FunctionImpl|, below. // // EXAMPLES // // - // https://fuchsia.googlesource.com/fuchsia/+/HEAD/sdk/lib/fit/test/examples/function_example1.cc // - // https://fuchsia.googlesource.com/fuchsia/+/HEAD/sdk/lib/fit/test/examples/function_example2.cc // template using function = FunctionImpl; // A move-only callable object wrapper that forces callables to be stored inline // and never performs heap allocation. // // Behaves just like |fit::Function| except that // attempting to store a target larger than |inlineTargetSize| will fail to // compile. template using InlineFunction = FunctionImpl; // Synonym for a function which takes no arguments and produces no result. using closure = function; // A |fit::Callback| is a move-only polymorphic function wrapper that also // ensures "run-once" semantics (such as callbacks shared with timeouts, or for // service requests with redundant, failover, or fallback service providers). // A |fit::Callback| releases it's resources after the first call, and can be // inspected before calling, so a potential caller can know if it should call // the function, or skip the call because the target was already called. // // If you need a move-only function class with typical function characteristics, // that permits multiple invocations of the same function, see |fit::Function|. // // |fit::Callback| behaves like |std::function| except: // // 1. It is move-only instead of copyable, so it can hold targets that cannot // be copied, such as mutable lambdas, and immutable lambdas that capture // move-only objects. // 2. On the first call to invoke a |fit::Callback|, the target function held // by the |fit::Callback| cannot be called again. // // When a |fit::Callback| is invoked for the first time, the target function is // released and destructed, along with any resources owned by that function // (typically the objects captured by a lambda). // // A |fit::Callback| in the "already called" state has the same state as a // |fit::Callback| that has been assigned to |nullptr|. It can be compared to // |nullptr| (via "==" or "!=", and its "operator bool()" returns false, which // provides a convenient way to gate whether or not the |fit::Callback| should // be called. (Note that invoking an empty |fit::Callback| or |fit::Function| // will cause a program abort!) // // As an example, sharing |fit::Callback| between both a service and a timeout // might look something like this: // // void service_with_timeout(fit::Callback cb, uint timeout_ms) { // service_request([cb = cb.share()]() mutable { if (cb) cb(false); }); // timeout(timeout_ms, [cb = std::move(cb)]() mutable { if (cb) cb(true); }); // } // // Since |fit::Callback| objects are move-only, and not copyable, duplicate // references to the same |fit::Callback| can be obtained via share(), as shown // in the example above. This method converts the |fit::Callback| into a // reference-counted version of the |fit::Callback| and returns a copy of the // reference as another |fit::Callback| with the same target function. // // What is notable about |fit::Callback.share()| is that invoking any shared // copy will "nullify" all shared copies, as shown in the example. // // Note that |fit::Callback| is NOT thread-safe by default. If multi-threaded // support is required, you would need to implement your own mutex, or similar // guard, before checking and calling a |fit::Callback|. // // Targets of up to |inlineTargetSize| bytes in size (rounded up for memory // alignment) are stored inline within the callback object without incurring // any heap allocation. Larger callable objects will be moved to the heap as // required. // // See also |fit::inline_callback| for more control over allocation // behavior. // // SYNOPSIS // // |T| is the callback's signature. e.g. void(int, std::string). // // |inlineTargetSize| is the minimum size of target that is guaranteed to // fit within a callback without requiring heap allocation. // Defaults to |kDefaultInlineTargetSize|. // // Class members are documented in |fit::CallbackImpl|, below. // template using Callback = CallbackImpl; // A move-only, run-once, callable object wrapper that forces callables to be // stored inline and never performs heap allocation. // // Behaves just like |fit::Callback| except that // attempting to store a target larger than |inlineTargetSize| will fail to // compile. template using InlineCallback = CallbackImpl; template class FunctionImpl final : private gfxstream::guest::fit::internal:: function_base { using Base = gfxstream::guest::fit::internal:: function_base; // function_base requires private access during share() friend class gfxstream::guest::fit::internal:: function_base; // supports target() for shared functions friend const void* gfxstream::guest::fit::internal::get_target_type_id<>( const FunctionImpl&); template using NotSelfType = gfxstream::guest::fit::internal::NotSameType; template using RequiresConditions = gfxstream::guest::fit::internal::RequiresConditions; template using AssignmentRequiresConditions = gfxstream::guest::fit::internal::AssignmentRequiresConditions; public: // The function's result type. using typename Base::result_type; // Initializes an empty (null) function. Attempting to call an empty // function will abort the program. FunctionImpl() = default; // Creates a function with an empty target (same outcome as the default // constructor). FunctionImpl(decltype(nullptr)) : Base(nullptr) {} // Creates a function bound to the specified function pointer. // If target == nullptr, assigns an empty target. FunctionImpl(Result (*target)(Args...)) : Base(target) {} // Creates a function bound to the specified callable object. // If target == nullptr, assigns an empty target. // // For functors, we need to capture the raw type but also restrict on the // existence of an appropriate operator () to resolve overloads and implicit // casts properly. // // Note that specializations of this template method that take fit::Callback // objects as the target Callable are deleted (see below). template ()(std::declval()...)), result_type>, NotSelfType> = true> FunctionImpl(Callable&& target) : Base(std::forward(target)) {} // Deletes the specializations of FunctionImpl(Callable) that would allow // a |fit::Function| to be constructed from a |fit::Callback|. This prevents // unexpected behavior of a |fit::Function| that would otherwise fail after // one call. To explicitly allow this, simply wrap the |fit::Callback| in a // pass-through lambda before passing it to the |fit::Function|. template FunctionImpl(gfxstream::guest::fit::CallbackImpl) = delete; // Creates a function with a target moved from another function, // leaving the other function with an empty target. FunctionImpl(FunctionImpl&& other) : Base(static_cast(other)) {} // Destroys the function, releasing its target. ~FunctionImpl() = default; // Assigns the function to an empty target. Attempting to invoke the // function will abort the program. FunctionImpl& operator=(decltype(nullptr)) { Base::assign(nullptr); return *this; } // Assigns the function to the specified callable object. If target == // nullptr, assigns an empty target. // // For functors, we need to capture the raw type but also restrict on the // existence of an appropriate operator () to resolve overloads and implicit // casts properly. // // Note that specializations of this template method that take fit::Callback // objects as the target Callable are deleted (see below). template AssignmentRequiresConditions< std::is_convertible()(std::declval()...)), result_type>, NotSelfType> operator=(Callable&& target) { Base::assign(std::forward(target)); return *this; } // Deletes the specializations of operator=(Callable) that would allow // a |fit::Function| to be assigned from a |fit::Callback|. This // prevents unexpected behavior of a |fit::Function| that would otherwise // fail after one call. To explicitly allow this, simply wrap the // |fit::Callback| in a pass-through lambda before assigning it to the // |fit::Function|. template FunctionImpl& operator=(gfxstream::guest::fit::CallbackImpl) = delete; // Move assignment FunctionImpl& operator=(FunctionImpl&& other) { if (&other == this) return *this; Base::assign(static_cast(other)); return *this; } // Swaps the functions' targets. void swap(FunctionImpl& other) { Base::swap(other); } // Returns a pointer to the function's target. using Base::target; // Returns true if the function has a non-empty target. using Base::operator bool; // Invokes the function's target. // Aborts if the function's target is empty. Result operator()(Args... args) const { return Base::invoke(std::forward(args)...); } // Returns a new function object that invokes the same target. // The target itself is not copied; it is moved to the heap and its // lifetime is extended until all references have been released. // // Note: This method is not supported on |fit::InlineFunction<>| // because it may incur a heap allocation which is contrary to // the stated purpose of |fit::InlineFunction<>|. FunctionImpl share() { FunctionImpl copy; Base::template share_with(copy); return copy; } }; template void swap(FunctionImpl& a, FunctionImpl& b) { a.swap(b); } template bool operator==(const FunctionImpl& f, decltype(nullptr)) { return !f; } template bool operator==(decltype(nullptr), const FunctionImpl& f) { return !f; } template bool operator!=(const FunctionImpl& f, decltype(nullptr)) { return !!f; } template bool operator!=(decltype(nullptr), const FunctionImpl& f) { return !!f; } template class CallbackImpl final : private gfxstream::guest::fit::internal:: function_base { using Base = gfxstream::guest::fit::internal:: function_base; // function_base requires private access during share() friend class gfxstream::guest::fit::internal:: function_base; // supports target() for shared functions friend const void* gfxstream::guest::fit::internal::get_target_type_id<>( const CallbackImpl&); template using NotSelfType = gfxstream::guest::fit::internal::NotSameType; template using RequiresConditions = gfxstream::guest::fit::internal::RequiresConditions; template using AssignmentRequiresConditions = gfxstream::guest::fit::internal::AssignmentRequiresConditions; public: // The callback function's result type. using typename Base::result_type; // Initializes an empty (null) callback. Attempting to call an empty // callback will abort the program. CallbackImpl() = default; // Creates a callback with an empty target (same outcome as the default // constructor). CallbackImpl(decltype(nullptr)) : Base(nullptr) {} // Creates a callback bound to the specified function pointer. // If target == nullptr, assigns an empty target. CallbackImpl(Result (*target)(Args...)) : Base(target) {} // Creates a callback bound to the specified callable object. // If target == nullptr, assigns an empty target. // // For functors, we need to capture the raw type but also restrict on the // existence of an appropriate operator () to resolve overloads and implicit // casts properly. template ()(std::declval()...)), result_type>, NotSelfType> = true> CallbackImpl(Callable&& target) : Base(std::forward(target)) {} // Creates a callback with a target moved from another callback, // leaving the other callback with an empty target. CallbackImpl(CallbackImpl&& other) : Base(static_cast(other)) {} // Destroys the callback, releasing its target. ~CallbackImpl() = default; // Assigns the callback to an empty target. Attempting to invoke the // callback will abort the program. CallbackImpl& operator=(decltype(nullptr)) { Base::assign(nullptr); return *this; } // Assigns the callback to the specified callable object. If target == // nullptr, assigns an empty target. // // For functors, we need to capture the raw type but also restrict on the // existence of an appropriate operator () to resolve overloads and implicit // casts properly. template AssignmentRequiresConditions< std::is_convertible()(std::declval()...)), result_type>, NotSelfType> operator=(Callable&& target) { Base::assign(std::forward(target)); return *this; } // Move assignment CallbackImpl& operator=(CallbackImpl&& other) { if (&other == this) return *this; Base::assign(static_cast(other)); return *this; } // Swaps the callbacks' targets. void swap(CallbackImpl& other) { Base::swap(other); } // Returns a pointer to the callback's target. using Base::target; // Returns true if the callback has a non-empty target. using Base::operator bool; // Invokes the callback's target. // Aborts if the callback's target is empty. // |fit::Callback| must be non-const to invoke. Before the target function // is actually called, the fit::Callback will be set to the default empty // state (== nullptr, and operator bool() will subsequently return |false|). // The target function will then be released after the function is called. // If the callback was shared, any remaining copies will also be cleared. Result operator()(Args... args) { auto temp = std::move(*this); return temp.invoke(std::forward(args)...); } // Returns a new callback object that invokes the same target. // The target itself is not copied; it is moved to the heap and its // lifetime is extended until all references have been released. // For |fit::Callback| (unlike fit::Function), the first invocation of the // callback will release all references to the target. All callbacks // derived from the same original callback (via share()) will be cleared, // as if set to |nullptr|, and "operator bool()" will return false. // // Note: This method is not supported on |fit::InlineFunction<>| // because it may incur a heap allocation which is contrary to // the stated purpose of |fit::InlineFunction<>|. CallbackImpl share() { CallbackImpl copy; Base::template share_with(copy); return copy; } }; template void swap(CallbackImpl& a, CallbackImpl& b) { a.swap(b); } template bool operator==(const CallbackImpl& f, decltype(nullptr)) { return !f; } template bool operator==(decltype(nullptr), const CallbackImpl& f) { return !f; } template bool operator!=(const CallbackImpl& f, decltype(nullptr)) { return !!f; } template bool operator!=(decltype(nullptr), const CallbackImpl& f) { return !!f; } // Returns a Callable object that invokes a member function of an object. template auto bindMember(T* instance, R (T::*fn)(Args...)) { return [instance, fn](Args... args) { return (instance->*fn)(std::forward(args)...); }; } } // namespace fit } // namespace gfxstream::guest