diff --git a/README.md b/README.md index 6574678..9a3e3cd 100644 --- a/README.md +++ b/README.md @@ -287,6 +287,32 @@ class Generator { If defined, the `destructor` callback is called first, and then the `T` destructor is called as usual. +To make use of polymorphism with resources, only the base class must be +declared using `FINE_RESOURCE`. To construct derived classes, use the +`fine::make_resource(...)` function: + +```c++ +class Supplier { +public: + virtual ~Supplier() noexcept = default; + + virtual std::int64_t supply() = 0; +}; +FINE_RESOURCE(Supplier); + +class ConstantSupplier final : public Supplier { +public: + ConstantSupplier(std::int64_t constant) : m_constant(constant) {} + + std::int64_t supply() { return m_constant; } + +private: + std::int64_t m_constant; +}; + +fine::ResourcePtr supplier = fine::make_resource(INT64_C(42)); +``` + Oftentimes NIFs deal with classes from third-party packages, in which case, you may not control how the objects are created and you cannot add callbacks such as `destructor` to the implementation. If you run diff --git a/include/fine.hpp b/include/fine.hpp index 4c64469..846b3cc 100644 --- a/include/fine.hpp +++ b/include/fine.hpp @@ -164,17 +164,36 @@ template class Error { namespace __private__ { template struct ResourceWrapper { - T resource; - bool initialized; + union { + struct { + bool initialized; + } data; + std::max_align_t _unused; + } payload; + + bool initialized() const { return payload.data.initialized; } + + void set_initialized(bool initialized) { + payload.data.initialized = initialized; + } + + const T *resource() const { return reinterpret_cast(this + 1); } + + T *resource() noexcept { return reinterpret_cast(this + 1); } + + template >> + static constexpr std::size_t byte_size() noexcept { + return sizeof(ResourceWrapper) + sizeof(U); + } static void dtor(ErlNifEnv *env, void *ptr) { auto resource_wrapper = reinterpret_cast *>(ptr); - if (resource_wrapper->initialized) { + if (resource_wrapper->initialized()) { if constexpr (has_destructor::value) { - resource_wrapper->resource.destructor(env); + resource_wrapper->resource()->destructor(env); } - resource_wrapper->resource.~T(); + resource_wrapper->resource()->~T(); } } @@ -221,11 +240,11 @@ template class ResourcePtr { return *this; } - T &operator*() const { return this->ptr->resource; } + T &operator*() const { return *this->ptr->resource(); } - T *operator->() const { return &this->ptr->resource; } + T *operator->() const { return this->ptr->resource(); } - T *get() const { return &this->ptr->resource; } + T *get() const { return this->ptr->resource(); } friend void swap(ResourcePtr &left, ResourcePtr &right) { using std::swap; @@ -241,12 +260,17 @@ template class ResourcePtr { // Friend functions that use the resource_type static member or the // private constructor. - template + template friend ResourcePtr make_resource(Args &&...args); + template + friend Term make_resource_binary(ErlNifEnv *env, ResourcePtr resource, + const char *data, size_t size); + friend class Registration; friend struct Decoder>; + friend struct Encoder>; inline static ErlNifResourceType *resource_type = nullptr; @@ -255,7 +279,8 @@ template class ResourcePtr { // Allocates a new resource object, invoking its constructor with the // given arguments. -template +template >> ResourcePtr make_resource(Args &&...args) { auto type = ResourcePtr::resource_type; @@ -265,10 +290,10 @@ ResourcePtr make_resource(Args &&...args) { " to register your resource type with the FINE_RESOURCE macro"); } - void *allocation_ptr = - enif_alloc_resource(type, sizeof(__private__::ResourceWrapper)); + void *allocation_ptr = enif_alloc_resource( + type, __private__::ResourceWrapper::template byte_size()); - auto resource_wrapper = + auto *resource_wrapper = reinterpret_cast<__private__::ResourceWrapper *>(allocation_ptr); // We create ResourcePtr right away, to make sure the resource is @@ -278,13 +303,14 @@ ResourcePtr make_resource(Args &&...args) { // We use a wrapper struct with an extra field to track if the // resource has actually been initialized. This way if the constructor // below throws, we can skip the destructor calls in the Erlang dtor - resource_wrapper->initialized = false; + resource_wrapper->set_initialized(false); // Invoke the constructor with prefect forwarding to initialize the // object at the VM-allocated memory - new (&resource_wrapper->resource) T(std::forward(args)...); + new (reinterpret_cast(resource_wrapper->resource())) + U(std::forward(args)...); - resource_wrapper->initialized = true; + resource_wrapper->set_initialized(true); return resource; } @@ -296,8 +322,8 @@ ResourcePtr make_resource(Args &&...args) { template Term make_resource_binary(ErlNifEnv *env, ResourcePtr resource, const char *data, size_t size) { - return enif_make_resource_binary( - env, reinterpret_cast(resource.get()), data, size); + return enif_make_resource_binary(env, reinterpret_cast(resource.ptr), + data, size); } // Creates a binary term copying data from the given buffer. @@ -811,7 +837,7 @@ template struct Encoder> { template struct Encoder> { static ERL_NIF_TERM encode(ErlNifEnv *env, const ResourcePtr &resource) { - return enif_make_resource(env, reinterpret_cast(resource.get())); + return enif_make_resource(env, reinterpret_cast(resource.ptr)); } }; diff --git a/test/c_src/finest.cpp b/test/c_src/finest.cpp index ddb3cd2..1c408fb 100644 --- a/test/c_src/finest.cpp +++ b/test/c_src/finest.cpp @@ -76,6 +76,30 @@ struct ExError { static constexpr auto is_exception = true; }; +struct AbstractRes { + virtual ~AbstractRes() noexcept = default; +}; +FINE_RESOURCE(AbstractRes); + +struct ConcreteRes : AbstractRes { + ErlNifPid pid; + + ConcreteRes(ErlNifPid pid) : pid(pid) {} + + ~ConcreteRes() noexcept override { + auto target_pid = this->pid; + + auto thread = std::thread([target_pid] { + auto msg_env = enif_alloc_env(); + auto msg = fine::encode(msg_env, atoms::destructor_default); + enif_send(NULL, &target_pid, msg_env, msg); + enif_free_env(msg_env); + }); + + thread.detach(); + } +}; + int64_t add(ErlNifEnv *, int64_t x, int64_t y) { return x + y; } FINE_NIF(add, 0); @@ -196,6 +220,11 @@ fine::Term resource_binary(ErlNifEnv *env, } FINE_NIF(resource_binary, 0); +fine::ResourcePtr resource_abstract(ErlNifEnv *, ErlNifPid pid) { + return fine::make_resource(pid); +} +FINE_NIF(resource_abstract, 0); + fine::Term make_new_binary(ErlNifEnv *env) { const char *buffer = "hello world"; size_t size = 11; diff --git a/test/lib/finest/nif.ex b/test/lib/finest/nif.ex index 371ade0..2da3dad 100644 --- a/test/lib/finest/nif.ex +++ b/test/lib/finest/nif.ex @@ -41,6 +41,7 @@ defmodule Finest.NIF do def resource_create(_pid), do: err!() def resource_get(_resource), do: err!() def resource_binary(_resource), do: err!() + def resource_abstract(_pid), do: err!() def make_new_binary(), do: err!() diff --git a/test/test/finest_test.exs b/test/test/finest_test.exs index dc3bc9b..64d7cef 100644 --- a/test/test/finest_test.exs +++ b/test/test/finest_test.exs @@ -252,6 +252,13 @@ defmodule FinestTest do assert_receive :destructor_default end + + test "resource can be abstract" do + NIF.resource_abstract(self()) + :erlang.garbage_collect(self()) + + assert_receive :destructor_default + end end describe "make_new_binary" do