/* SPDX-License-Identifier: GPL-2.0-or-later */ #pragma once /** \file * \ingroup bli * * A `ResourceScope` takes ownership of arbitrary data/resources. Those resources will be * destructed and/or freed when the `ResourceScope` is destructed. Destruction happens in reverse * order. That allows resources do depend on other resources that have been added before. * * A `ResourceScope` can also be thought of as a dynamic/runtime version of normal scopes in C++ * that are surrounded by braces. * * The main purpose of a `ResourceScope` is to allow functions to inject data into the scope of the * caller. Traditionally, that can only be done by returning a value that owns everything it needs. * This is fine until one has to deal with optional ownership. There are many ways to have a type * optionally own something else, all of which are fairly annoying. A `ResourceScope` can be used * to avoid having to deal with optional ownership. If some value would be owned, it can just be * added to the resource scope, otherwise not. * * When a function takes a `ResourceScope` as parameter, it usually means that its return value * will live at least as long as the passed in resources scope. However, it might also live longer. * That can happen when the function returns a reference to statically allocated data or * dynamically allocated data depending on some condition. */ #include "BLI_linear_allocator.hh" #include "BLI_utility_mixins.hh" #include "BLI_vector.hh" namespace blender { class ResourceScope : NonCopyable, NonMovable { private: struct ResourceData { void *data; void (*free)(void *data); }; LinearAllocator<> allocator_; Vector resources_; public: ResourceScope(); ~ResourceScope(); template T *add(std::unique_ptr resource); template T *add(destruct_ptr resource); void add(void *userdata, void (*free)(void *)); template T &add_value(T &&value); template void add_destruct_call(Func func); template T &construct(Args &&...args); LinearAllocator<> &linear_allocator(); }; /* -------------------------------------------------------------------- */ /** \name #ResourceScope Inline Methods * \{ */ /** * Pass ownership of the resource to the ResourceScope. It will be destructed and freed when * the collector is destructed. */ template inline T *ResourceScope::add(std::unique_ptr resource) { T *ptr = resource.release(); if (ptr == nullptr) { return nullptr; } this->add(ptr, [](void *data) { T *typed_data = reinterpret_cast(data); delete typed_data; }); return ptr; } /** * Pass ownership of the resource to the ResourceScope. It will be destructed when the * collector is destructed. */ template inline T *ResourceScope::add(destruct_ptr resource) { T *ptr = resource.release(); if (ptr == nullptr) { return nullptr; } /* There is no need to keep track of such types. */ if constexpr (std::is_trivially_destructible_v) { return ptr; } this->add(ptr, [](void *data) { T *typed_data = reinterpret_cast(data); typed_data->~T(); }); return ptr; } /** * Pass ownership of some resource to the ResourceScope. The given free function will be * called when the collector is destructed. */ inline void ResourceScope::add(void *userdata, void (*free)(void *)) { ResourceData data; data.data = userdata; data.free = free; resources_.append(data); } /** * Construct an object with the same value in the ResourceScope and return a reference to the * new value. */ template inline T &ResourceScope::add_value(T &&value) { return this->construct(std::forward(value)); } /** * The passed in function will be called when the scope is destructed. */ template inline void ResourceScope::add_destruct_call(Func func) { void *buffer = allocator_.allocate(sizeof(Func), alignof(Func)); new (buffer) Func(std::move(func)); this->add(buffer, [](void *data) { (*static_cast(data))(); }); } /** * Utility method to construct an instance of type T that will be owned by the ResourceScope. */ template inline T &ResourceScope::construct(Args &&...args) { destruct_ptr value_ptr = allocator_.construct(std::forward(args)...); T &value_ref = *value_ptr; this->add(std::move(value_ptr)); return value_ref; } /** * Returns a reference to a linear allocator that is owned by the ResourcesCollector. Memory * allocated through this allocator will be freed when the collector is destructed. */ inline LinearAllocator<> &ResourceScope::linear_allocator() { return allocator_; } /** \} */ } // namespace blender