Files
integration/luprex/core/cpp/eng-malloc.hpp

193 lines
5.6 KiB
C++
Raw Normal View History

2022-02-28 21:57:54 -05:00
//
// 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:
//
2022-03-02 14:52:51 -05:00
// * When writing a class, always derive from eng::opnew. This adds a
// custom operator new to your class, which causes your class to be
// allocated using eng::malloc.
2022-02-28 21:57:54 -05:00
//
2022-03-02 14:52:51 -05:00
// * When using STL containers, you need to use the eng variant:
// eng::map, eng::set, eng::vector, eng::unordered_map, eng::unordered_set,
// and eng::deque. These classes derive from eng::opnew, and they also
// use eng::malloc for their internal nodes.
2022-02-28 21:57:54 -05:00
//
2022-03-02 14:52:51 -05:00
// * Use eng::string instead of std::string. Use eng::ostringstream
// instead of std::ostringstream.
2022-02-28 21:57:54 -05:00
//
// * Simple classes like std::pair, std::string_view, std::less, std::hash, and
2022-03-02 14:52:51 -05:00
// so forth are not wrapped. Do not use operator new or delete on
// these classes.
//
// * Instead of std::make_shared, use eng::make_shared. You need this
// because std::make_shared doesn't respect your custom operator new.
2022-02-28 21:57:54 -05:00
//
// * Be aware that most C++ streams use the system malloc heap, and there's no
2022-03-02 14:52:51 -05:00
// way to change that. Fortunately, eng::ostringstream uses the eng::malloc
2022-02-28 21:57:54 -05:00
// 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>
2022-03-02 14:52:51 -05:00
#include <cassert>
2022-02-28 21:57:54 -05:00
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(p); }
2022-02-28 21:57:54 -05:00
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);
}
2022-03-02 14:52:51 -05:00
void *operator new[](size_t size)
{
return ::eng::malloc(size);
}
void operator delete[](void *p, size_t size)
{
return ::eng::free(p);
}
2022-02-28 21:57:54 -05:00
};
} // namespace eng
2022-03-02 14:52:51 -05:00
// eng::nevernew. A class containing private operator new and
// operator delete, making it impossible to 'new' the class.
namespace eng {
class nevernew {
private:
void *operator new(size_t size)
{
assert(false && "not supposed to 'new' this class");
return NULL;
}
void operator delete(void *p, size_t size)
{
assert(false && "not supposed to 'delete' this class");
}
void *operator new[](size_t size)
{
assert(false && "not supposed to 'new' this class");
return NULL;
}
void operator delete[](void *p, size_t size)
{
assert(false && "not supposed to 'delete' this class");
}
};
} // namespace eng
2022-02-28 21:57:54 -05:00
// 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) {
2022-03-02 14:52:51 -05:00
return std::allocate_shared<T>(eng::allocator<T>(), args...);
2022-02-28 21:57:54 -05:00
}
2022-03-02 14:52:51 -05:00
} // namespace eng
2022-02-28 21:57:54 -05:00
2022-03-02 14:52:51 -05:00
// 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.
2022-02-28 21:57:54 -05:00
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