#pragma once
#include <cstddef>
#include <memory>
#include <memory_resource>
#include <utility>
class Scene_Memory_Domain {
public:
    explicit Scene_Memory_Domain(std::pmr::memory_resource& upstream) : upstream_(&upstream), resource_(&upstream) {}
    std::pmr::memory_resource& resource() noexcept {
        return resource_;
    }
    std::pmr::memory_resource& upstream() noexcept {
        return *upstream_;
    }
private:
    std::pmr::memory_resource* const upstream_;
    std::pmr::synchronized_pool_resource resource_;
};
template <class Value>
class Scene_Memory_Allocator {
public:
    using value_type = Value;
    template <class Other>
    struct rebind {
        using other = Scene_Memory_Allocator<Other>;
    };
    Scene_Memory_Allocator() = delete;
    explicit Scene_Memory_Allocator(std::shared_ptr<Scene_Memory_Domain> domain) noexcept : domain_(std::move(domain)) {}
    template <class Other>
    Scene_Memory_Allocator(const Scene_Memory_Allocator<Other>& other) noexcept : domain_(other.domain_) {}
    Value* allocate(std::size_t count) {
        return static_cast<Value*>(domain_->resource().allocate(count * sizeof(Value), alignof(Value)));
    }
    void deallocate(Value* pointer, std::size_t count) noexcept {
        domain_->resource().deallocate(pointer, count * sizeof(Value), alignof(Value));
    }
    template <class Other>
    bool operator==(const Scene_Memory_Allocator<Other>& other) const noexcept {
        return domain_ == other.domain_;
    }
private:
    template <class>
    friend class Scene_Memory_Allocator;
    std::shared_ptr<Scene_Memory_Domain> domain_;
};
template <class Value>
struct Memory_Resource_Deleter {
    std::pmr::memory_resource* resource{std::pmr::get_default_resource()};
    void operator()(Value* value) const noexcept {
        std::destroy_at(value);
        resource->deallocate(value, sizeof(Value), alignof(Value));
    }
};
template <class Value>
using Memory_Resource_Unique_Ptr = std::unique_ptr<Value, Memory_Resource_Deleter<Value>>;
template <class Value, class... Args>
Memory_Resource_Unique_Ptr<Value> make_memory_resource_unique(std::pmr::memory_resource& resource, Args&&... args) {
    void* storage = resource.allocate(sizeof(Value), alignof(Value));
    try {
        Value* value = std::construct_at(static_cast<Value*>(storage), std::forward<Args>(args)...);
        return Memory_Resource_Unique_Ptr<Value>(value, Memory_Resource_Deleter<Value>{&resource});
    } catch (...) {
        resource.deallocate(storage, sizeof(Value), alignof(Value));
        throw;
    }
}
