blob: 7a0afcb286f46d4e683470572ea4606f09a5ebe4 [file] [log] [blame]
// Copyright 2018 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 <stdlib.h>
#include <string.h>
#include <memory>
#include "garnet/lib/overnet/vocabulary/status.h"
namespace overnet {
// Completion callback function: a move-only functor that ensures that the
// underlying callback is called once and only once
enum AllocatedCallback { ALLOCATED_CALLBACK };
template <class Arg, size_t kMaxPayload = sizeof(void*)>
class Callback {
public:
// Use a (manually constructed) vtable to encode what to do when a callback is
// made (or not)
// This has been observed to generate more efficient code than writing out a
// C++ style vtable
struct VTable {
void (*move)(void* dst, void* src);
void (*call)(void* env, Arg&& arg);
void (*not_called)(void* env);
};
Callback() : vtable_(&null_vtable) {}
~Callback() { vtable_->not_called(&env_); }
template <class F,
typename = typename std::enable_if<sizeof(F) <= kMaxPayload>::type>
Callback(F&& f) {
vtable_ = &SmallFunctor<F>::vtable;
SmallFunctor<F>::InitEnv(&env_, std::forward<F>(f));
}
template <class F>
Callback(AllocatedCallback, F&& f)
: Callback([pf = new F(std::forward<F>(f))](Arg&& arg) {
auto fn = pf;
(*fn)(std::forward<Arg>(arg));
delete fn;
}){};
Callback(const Callback&) = delete;
Callback& operator=(const Callback&) = delete;
Callback(Callback&& other) {
vtable_ = other.vtable_;
other.vtable_ = &null_vtable;
vtable_->move(&env_, &other.env_);
}
Callback& operator=(Callback&& other) {
vtable_->not_called(&env_);
vtable_ = other.vtable_;
other.vtable_ = &null_vtable;
vtable_->move(&env_, &other.env_);
return *this;
}
void operator()(Arg arg) {
const auto* const vtable = vtable_;
vtable_ = &null_vtable;
vtable->call(&env_, std::forward<Arg>(arg));
}
bool empty() const { return vtable_ == &null_vtable; }
static Callback Ignored() {
return [](const Arg&) {};
}
static Callback Unimplemented() {
return [](const Arg&) { abort(); };
}
static Callback MustSucceed() {
return [](const Arg& arg) { assert(arg.is_ok()); };
}
private:
static void NullVTableMove(void*, void*) {}
static void NullVTableNotCalled(void*) {}
static void NullVTableCall(void*, Arg&&) { abort(); }
static const VTable null_vtable;
template <class F>
class SmallFunctor {
public:
static const VTable vtable;
static void InitEnv(void* env, F&& f) { new (env) F(std::forward<F>(f)); }
private:
static void Move(void* dst, void* src) {
F* f = static_cast<F*>(src);
new (dst) F(std::move(*f));
f->~F();
}
static void Call(void* env, Arg&& arg) {
F* f = static_cast<F*>(env);
(*f)(std::forward<Arg>(arg));
f->~F();
}
static void NotCalled(void* env) {
Call(env, Arg(StatusCode::CANCELLED, __PRETTY_FUNCTION__));
}
};
const VTable* vtable_;
typename std::aligned_storage<kMaxPayload>::type env_;
};
typedef Callback<Status> StatusCallback;
template <class T>
using StatusOrCallback = Callback<StatusOr<T>>;
template <class Arg, size_t kMaxPayload>
const typename Callback<Arg, kMaxPayload>::VTable
Callback<Arg, kMaxPayload>::null_vtable = {NullVTableMove, NullVTableCall,
NullVTableNotCalled};
template <class Arg, size_t kMaxPayload>
template <class F>
const typename Callback<Arg, kMaxPayload>::VTable
Callback<Arg, kMaxPayload>::SmallFunctor<F>::vtable = {Move, Call,
NotCalled};
template <size_t kMaxPayload>
class Callback<void, kMaxPayload> {
public:
// Use a (manually constructed) vtable to encode what to do when a callback is
// made (or not)
// This has been observed to generate more efficient code than writing out a
// C++ style vtable
struct VTable {
void (*move)(void* dst, void* src);
void (*call)(void* env);
void (*not_called)(void* env);
};
Callback() : vtable_(&null_vtable) {}
~Callback() { vtable_->not_called(&env_); }
template <class F,
typename = typename std::enable_if<sizeof(F) <= kMaxPayload>::type>
Callback(F&& f) {
vtable_ = &SmallFunctor<F>::vtable;
SmallFunctor<F>::InitEnv(&env_, std::forward<F>(f));
}
template <class F>
Callback(AllocatedCallback, F&& f)
: Callback([pf = new F(std::forward<F>(f))]() {
(*pf)();
delete pf;
}){};
Callback(const Callback&) = delete;
Callback& operator=(const Callback&) = delete;
Callback(Callback&& other) {
vtable_ = other.vtable_;
other.vtable_ = &null_vtable;
vtable_->move(&env_, &other.env_);
}
Callback& operator=(Callback&& other) {
vtable_->not_called(&env_);
vtable_ = other.vtable_;
other.vtable_ = &null_vtable;
vtable_->move(&env_, &other.env_);
return *this;
}
void operator()() {
const auto* const vtable = vtable_;
vtable_ = &null_vtable;
vtable->call(&env_);
}
bool empty() const { return vtable_ == &null_vtable; }
static Callback Ignored() {
return []() {};
}
static Callback Unimplemented() {
return []() { abort(); };
}
private:
static void NullVTableMove(void*, void*) {}
static void NullVTableNotCalled(void*) {}
static void NullVTableCall(void*) { abort(); }
static const VTable null_vtable;
template <class F>
class SmallFunctor {
public:
static const VTable vtable;
static void InitEnv(void* env, F&& f) { new (env) F(std::forward<F>(f)); }
private:
static void Move(void* dst, void* src) {
F* f = static_cast<F*>(src);
new (dst) F(std::move(*f));
f->~F();
}
static void Call(void* env) {
F* f = static_cast<F*>(env);
(*f)();
f->~F();
}
static void NotCalled(void* env) { Call(env); }
};
const VTable* vtable_;
typename std::aligned_storage<kMaxPayload>::type env_;
};
template <size_t kMaxPayload>
const typename Callback<void, kMaxPayload>::VTable
Callback<void, kMaxPayload>::null_vtable = {NullVTableMove, NullVTableCall,
NullVTableNotCalled};
template <size_t kMaxPayload>
template <class F>
const typename Callback<void, kMaxPayload>::VTable
Callback<void, kMaxPayload>::SmallFunctor<F>::vtable = {Move, Call,
NotCalled};
} // namespace overnet