Lots more work on eng::malloc
This commit is contained in:
152
luprex/core/cpp/eng-malloc.hpp
Normal file
152
luprex/core/cpp/eng-malloc.hpp
Normal file
@@ -0,0 +1,152 @@
|
||||
//
|
||||
// eng-malloc
|
||||
//
|
||||
// The engine has its own private eng::malloc which it uses to allocate
|
||||
// everything. The engine's malloc heap only contains engine data structures.
|
||||
// This helps achieve determinism when playing a replay log.
|
||||
//
|
||||
// The engine's eng::malloc is a thin wrapper around Doug Lea's Malloc, a good
|
||||
// general-purpose single-threaded malloc. It's probably not the fastest any
|
||||
// more (it was, once), but it's still quite good. It's also fairly easy
|
||||
// to work with.
|
||||
//
|
||||
// In order to get all engine data structures into the eng::malloc heap, you
|
||||
// need to jump through quite a few hoops:
|
||||
//
|
||||
// * When using STL classes, you need to use the 'eng' variant of those classes:
|
||||
//
|
||||
// - eng::string instead of std::string (include "wrap-string.hpp")
|
||||
// - eng::vector instead of std::vector (include "wrap-vector.hpp")
|
||||
// - eng::map instead of std::map (include "wrap-map.hpp")
|
||||
// - etc.
|
||||
//
|
||||
// These eng classes allocate memory using eng::malloc instead of malloc.
|
||||
//
|
||||
// * All your classes must derive from eng::opnew. This adds a custom operator
|
||||
// new and operator delete to your class, which allocate using eng::malloc.
|
||||
//
|
||||
// * Use eng::make_shared and eng::make_unique instead of std::make_shared and
|
||||
// std::make_unique.
|
||||
//
|
||||
// * Simple classes like std::pair, std::string_view, std::less, std::hash, and
|
||||
// so forth don't allocate memory. Those classes are not wrapped. There is
|
||||
// no eng::pair, eng::less, etc.
|
||||
//
|
||||
// * Be aware that most C++ streams use the system malloc heap, and there's no
|
||||
// way to change that. Fortunately, eng::ostringstream uses the dlmalloc
|
||||
// heap.
|
||||
//
|
||||
// * Failing to jump through all these hoops won't break your code in any
|
||||
// obvious way - you'll just have some of your data structures in the malloc
|
||||
// heap instead of the eng::malloc heap. This can break determinism of
|
||||
// replay.
|
||||
//
|
||||
#ifndef ENG_MALLOC_HPP
|
||||
#define ENG_MALLOC_HPP
|
||||
|
||||
#include <cstddef>
|
||||
#include <memory>
|
||||
|
||||
namespace eng {
|
||||
#ifdef __linux__
|
||||
void* malloc(size_t x);
|
||||
void free(void *p);
|
||||
void* realloc(void*, size_t);
|
||||
int memhash();
|
||||
#else
|
||||
inline void *malloc(size_t x) { return ::malloc(x); }
|
||||
inline void free(void *p) { return ::free(x); }
|
||||
inline void *realloc(void *p, size_t x) { return ::realloc(p, x); }
|
||||
inline int memhash() { return 0; }
|
||||
#endif
|
||||
} // namespace eng
|
||||
|
||||
// An allocator for lua states that uses eng::malloc and eng::free
|
||||
namespace eng {
|
||||
inline void *l_alloc(void *ud, void *ptr, size_t osize, size_t nsize) {
|
||||
if (nsize == 0) {
|
||||
::eng::free(ptr);
|
||||
return NULL;
|
||||
} else {
|
||||
return ::eng::realloc(ptr, nsize);
|
||||
}
|
||||
}
|
||||
} // namespace eng
|
||||
|
||||
// eng_allocator is similar to std::allocator, but allocates
|
||||
// objects using eng::malloc and eng::free.
|
||||
template <class T>
|
||||
class eng_allocator
|
||||
{
|
||||
public:
|
||||
using value_type = T;
|
||||
eng_allocator() noexcept {}
|
||||
template <class U> eng_allocator(eng_allocator<U> const&) noexcept {}
|
||||
|
||||
value_type* allocate(std::size_t n)
|
||||
{
|
||||
return static_cast<value_type*>(eng::malloc(n*sizeof(value_type)));
|
||||
}
|
||||
|
||||
void deallocate(value_type* p, std::size_t) noexcept
|
||||
{
|
||||
eng::free(p);
|
||||
}
|
||||
};
|
||||
|
||||
// Another name for eng_allocator is eng::allocator.
|
||||
namespace eng {
|
||||
template<class T>
|
||||
using allocator = ::eng_allocator<T>;
|
||||
} // namespace eng
|
||||
|
||||
// Mandated equality and inequality operators for eng_allocator.
|
||||
template <class T, class U>
|
||||
bool operator==(const eng_allocator<T> &, const eng_allocator<U> &) noexcept
|
||||
{
|
||||
return true;
|
||||
}
|
||||
template <class T, class U>
|
||||
bool operator!=(const eng_allocator<T> &, const eng_allocator<U> &) noexcept
|
||||
{
|
||||
return false;
|
||||
}
|
||||
|
||||
// eng::opnew. A class containing operator new and operator delete,
|
||||
// meant to be used as a base class for inheritance.
|
||||
namespace eng {
|
||||
class opnew {
|
||||
public:
|
||||
void *operator new(size_t size)
|
||||
{
|
||||
return ::eng::malloc(size);
|
||||
}
|
||||
|
||||
void operator delete(void *p, size_t size)
|
||||
{
|
||||
return ::eng::free(p);
|
||||
}
|
||||
};
|
||||
} // namespace eng
|
||||
|
||||
// eng::make_shared allocates shared objects using eng::malloc.
|
||||
namespace eng {
|
||||
template<class T, class... Args>
|
||||
inline ::std::shared_ptr<T> make_shared(Args&&... args) {
|
||||
static eng::allocator<T> alloc;
|
||||
return std::allocate_shared<T>(alloc, args...);
|
||||
}
|
||||
}
|
||||
|
||||
// eng::make_unique doesn't do anything different than std::make_unique:
|
||||
// they both use operator new and delete. You must
|
||||
// derive from eng::opnew to change operator new and delete.
|
||||
namespace eng {
|
||||
template<class T, class... Args>
|
||||
inline ::std::unique_ptr<T> make_unique(Args&&... args) {
|
||||
return std::make_unique<T>(args...);
|
||||
}
|
||||
} // namespace eng
|
||||
|
||||
#endif // ENG_MALLOC_HPP
|
||||
|
||||
Reference in New Issue
Block a user