Skip to content

File Memory.h

File List > Amplitude > Core > Memory.h

Go to the documentation of this file

// Copyright (c) 2021-present Sparky Studios. All rights reserved.
//
// 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.

#pragma once

#ifndef _AM_CORE_MEMORY_H
#define _AM_CORE_MEMORY_H

#include <SparkyStudios/Audio/Amplitude/Core/Common.h>

#include <memory>

#if !defined(AM_NO_MEMORY_STATS)
#include <atomic>
#include <mutex>
#include <set>
#include <unordered_map>
#endif

#define amMemory SparkyStudios::Audio::Amplitude::MemoryManager::GetInstance()

#define ampoolmalloc(_pool_, _size_) amMemory->Malloc(_pool_, _size_, __FILE__, __LINE__)

#define ampoolmalign(_pool_, _size_, _alignment_) amMemory->Malign(_pool_, _size_, _alignment_, __FILE__, __LINE__)

#define ampoolrealloc(_pool_, _ptr_, _size_) amMemory->Realloc(_pool_, _ptr_, _size_, __FILE__, __LINE__)

#define ampoolrealign(_pool_, _ptr_, _size_, _alignment_) amMemory->Realign(_pool_, _ptr_, _size_, _alignment_, __FILE__, __LINE__)

#define ampoolfree(_pool_, _ptr_) amMemory->Free(_pool_, _ptr_)

#define ammalloc(_size_) ampoolmalloc(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _size_)

#define ammalign(_size_, _alignment_) ampoolmalign(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _size_, _alignment_)

#define amrealloc(_ptr_, _size_) ampoolrealloc(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _ptr_, _size_)

#define amrealign(_ptr_, _size_, _alignment_)                                                                                              \
    ampoolrealign(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _ptr_, _size_, _alignment_)

#define amfree(_ptr_) ampoolfree(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _ptr_)

#define ampoolnew(_pool_, _type_, ...) new (ampoolmalign(_pool_, sizeof(_type_), alignof(_type_))) _type_(__VA_ARGS__)

#define ampooldelete(_pool_, _type_, _ptr_)                                                                                                \
    do                                                                                                                                     \
    {                                                                                                                                      \
        _type_* __amp_tmp = (_ptr_);                                                                                                       \
        if (__amp_tmp != nullptr)                                                                                                          \
        {                                                                                                                                  \
            __amp_tmp->~_type_();                                                                                                          \
            ampoolfree(_pool_, __amp_tmp);                                                                                                 \
        }                                                                                                                                  \
    } while (0)

#define amnew(_type_, ...) ampoolnew(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _type_, __VA_ARGS__)

#define amdelete(_type_, _ptr_) ampooldelete(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _type_, _ptr_)

#define ampoolunique(__pool__, __type__, ...) AmUniquePtr<__type__, __pool__>(ampoolnew(__pool__, __type__, __VA_ARGS__))

#define amunique(_type_, ...) ampoolunique(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _type_, __VA_ARGS__)

#define ampoolshared(__pool__, __type__, ...) AmSharedPtr<__type__, __pool__>::Make(__VA_ARGS__)

#define amshared(_type_, ...) ampoolshared(SparkyStudios::Audio::Amplitude::eMemoryPoolKind_Default, _type_, __VA_ARGS__)

namespace SparkyStudios::Audio::Amplitude
{
    enum eMemoryPoolKind : AmUInt8
    {
        eMemoryPoolKind_Engine,

        eMemoryPoolKind_Amplimix,

        eMemoryPoolKind_SoundData,

        eMemoryPoolKind_Filtering,

        eMemoryPoolKind_Codec,

        eMemoryPoolKind_IO,

        eMemoryPoolKind_Default,

        eMemoryPoolKind_COUNT,
    };

#if !defined(AM_NO_MEMORY_STATS)
    struct AM_API_PUBLIC MemoryPoolStats
    {
        eMemoryPoolKind pool;

        std::atomic<AmSize> maxMemoryUsed{};

        std::atomic<AmUInt64> allocCount{};

        std::atomic<AmUInt64> freeCount{};

        MemoryPoolStats()
            : MemoryPoolStats(eMemoryPoolKind_COUNT)
        {}

        explicit MemoryPoolStats(eMemoryPoolKind pool);

        MemoryPoolStats(const MemoryPoolStats& copy);

        MemoryPoolStats& operator=(const MemoryPoolStats& other);
    };
#endif

    class AM_API_PUBLIC MemoryAllocator
    {
    public:
        virtual ~MemoryAllocator() = default;

        virtual AmVoidPtr Malloc(eMemoryPoolKind pool, AmSize size) = 0;

        virtual AmVoidPtr Realloc(eMemoryPoolKind pool, AmVoidPtr address, AmSize size) = 0;

        virtual AmVoidPtr Malign(eMemoryPoolKind pool, AmSize size, AmUInt32 alignment) = 0;

        virtual AmVoidPtr Realign(eMemoryPoolKind pool, AmVoidPtr address, AmSize size, AmUInt32 alignment) = 0;

        virtual void Free(eMemoryPoolKind pool, AmVoidPtr address) = 0;

        virtual AmSize SizeOf(eMemoryPoolKind pool, AmVoidPtr address) = 0;
    };

    class AM_API_PUBLIC DefaultMemoryAllocator final : public MemoryAllocator
    {
    public:
        DefaultMemoryAllocator(AmUInt32 bucketsCount, AmSize bucketSizeInBytes);

        ~DefaultMemoryAllocator() override;

        AmVoidPtr Malloc(eMemoryPoolKind pool, AmSize size) override;

        AmVoidPtr Realloc(eMemoryPoolKind pool, AmVoidPtr address, AmSize size) override;

        AmVoidPtr Malign(eMemoryPoolKind pool, AmSize size, AmUInt32 alignment) override;

        AmVoidPtr Realign(eMemoryPoolKind pool, AmVoidPtr address, AmSize size, AmUInt32 alignment) override;

        void Free(eMemoryPoolKind pool, AmVoidPtr address) override;

        AmSize SizeOf(eMemoryPoolKind pool, AmVoidPtr address) override;

    private:
        /***
         * @brief Internal allocator spaces, for each pool.
         *
         * @internal
         */
        void* _allocators[eMemoryPoolKind_COUNT];
    };

    class AM_API_PUBLIC MemoryManager
    {
    public:
        struct Allocation
        {
            eMemoryPoolKind pool;

            AmVoidPtr address;

            AmSize size;

            const char* file;

            AmUInt32 line;

            [[nodiscard]] AM_INLINE explicit operator AmVoidPtr() const
            {
                return address;
            }

            [[nodiscard]] AM_INLINE bool operator==(const AmVoidPtr& ptr) const
            {
                return address == ptr;
            }

            [[nodiscard]] AM_INLINE bool operator==(const Allocation& other) const
            {
                return pool == other.pool && address == other.address;
            }

            [[nodiscard]] AM_INLINE bool operator<(const Allocation& other) const
            {
                return address < other.address;
            }
        };

        static void Initialize(std::unique_ptr<MemoryAllocator> allocator = nullptr);

        static void Deinitialize();

        [[maybe_unused]] static bool IsInitialized();

        static MemoryManager* GetInstance();

        [[nodiscard]] AmVoidPtr Malloc(eMemoryPoolKind pool, AmSize size, const char* file, AmUInt32 line);

        [[nodiscard]] AmVoidPtr Malign(eMemoryPoolKind pool, AmSize size, AmUInt32 alignment, const char* file, AmUInt32 line);

        [[nodiscard]] AmVoidPtr Realloc(eMemoryPoolKind pool, AmVoidPtr address, AmSize size, const char* file, AmUInt32 line);

        [[nodiscard]] AmVoidPtr Realign(
            eMemoryPoolKind pool, AmVoidPtr address, AmSize size, AmUInt32 alignment, const char* file, AmUInt32 line);

        void Free(eMemoryPoolKind pool, AmVoidPtr address);

        [[nodiscard]] AmSize SizeOf(eMemoryPoolKind pool, AmVoidPtr address) const;

#if !defined(AM_NO_MEMORY_STATS)
        [[nodiscard]] AmSize TotalReservedMemorySize(eMemoryPoolKind pool) const;

        [[nodiscard]] AmSize TotalReservedMemorySize() const;

        static AmString GetMemoryPoolName(eMemoryPoolKind pool);

        [[nodiscard]] const MemoryPoolStats& GetStats(eMemoryPoolKind pool) const;

        [[nodiscard]] AmString InspectMemoryLeaks() const;
#endif

    private:
        explicit MemoryManager(std::unique_ptr<MemoryAllocator> allocator);
        ~MemoryManager();

        std::unique_ptr<MemoryAllocator> _allocator;

#if !defined(AM_NO_MEMORY_STATS)
        void RemoveAllocation(const Allocation& allocation);
        void AddAllocation(const Allocation& allocation);

        mutable std::mutex _allocationsMutex;
        std::set<Allocation> _memAllocations;
        std::unordered_map<eMemoryPoolKind, MemoryPoolStats> _memPoolsStats;
#endif
    };

    class AM_API_PUBLIC ScopedMemoryAllocation
    {
    public:
        ScopedMemoryAllocation() = default;

        ScopedMemoryAllocation(eMemoryPoolKind pool, AmSize size, const char* file, AmUInt32 line);

        ScopedMemoryAllocation(eMemoryPoolKind pool, AmSize size, AmUInt32 alignment, const char* file, AmUInt32 line);

        ScopedMemoryAllocation(const ScopedMemoryAllocation&) = delete;

        ScopedMemoryAllocation& operator=(const ScopedMemoryAllocation&) = delete;

        ScopedMemoryAllocation(ScopedMemoryAllocation&& other) noexcept;

        ScopedMemoryAllocation& operator=(ScopedMemoryAllocation&& other) noexcept;

        ~ScopedMemoryAllocation();

        template<typename T>
        [[nodiscard]] AM_INLINE T* PointerOf() const
        {
            return static_cast<T*>(_address);
        }

        template<typename T, typename std::enable_if_t<std::is_pointer_v<T>, bool> = false>
        [[nodiscard]] AM_INLINE T As() const
        {
            return reinterpret_cast<T>(_address);
        }

        [[nodiscard]] AM_INLINE AmVoidPtr Address() const
        {
            return _address;
        }

    private:
        eMemoryPoolKind _pool = eMemoryPoolKind_Default;
        void* _address = nullptr;
    };

    template<class T, eMemoryPoolKind Pool = eMemoryPoolKind_Default>
    struct am_delete
    {
        constexpr am_delete() noexcept = default;

        AM_INLINE void operator()(T* ptr) const noexcept
        {
            static_assert(!std::is_void_v<T>, "Cannot delete a void pointer.");
            ampooldelete(Pool, T, ptr);
        }
    };

    template<class T, eMemoryPoolKind Pool = eMemoryPoolKind_Default>
    using AmUniquePtr = std::unique_ptr<T, am_delete<T, Pool>>;

    template<class T, eMemoryPoolKind Pool = eMemoryPoolKind_Default>
    class AmSharedPtr : public std::shared_ptr<T>
    {
    public:
        template<class... Args>
        static AmSharedPtr<T, Pool> Make(Args&&... args)
        {
            return AmSharedPtr<T, Pool>(ampoolnew(Pool, T, std::forward<Args>(args)...));
        }

        explicit AmSharedPtr(T* ptr)
            : std::shared_ptr<T>(ptr, am_delete<T, Pool>{})
        {}
    };

    template<typename T>
    class AmFakeSharedPtr : public std::shared_ptr<T>
    {
        struct am_fake_delete
        {
            constexpr am_fake_delete() noexcept = default;

            void operator()(T*) const
            {}
        };

    public:
        explicit AmFakeSharedPtr(T* ptr)
            : std::shared_ptr<T>(ptr, am_fake_delete{})
        {}
    };
} // namespace SparkyStudios::Audio::Amplitude

#endif // _AM_CORE_MEMORY_H