/**
*
* \file Asura_MemoryManagement.h
*  
* Created by Kevin Lea - 10/10/2001 15:13:46
*   
* Copyright (C) 2001 Rebellion, All Rights Reserved.
*  
*   
* \brief	This memory management system is intended to replace the functionality
* of new and delete and to provide data on memory usage, detect memory leaks,
* out-of-bounds violations etc..
*	  
*		The 'standard' approach to overriding memory calls is to either use
* separate replacement functions (e.g. MyNew) or to use macros to redirect the 
* calls. With C++ we have the option of overloading new and delete, which has the
* cute side-effect of catching memory allocations for static variables - handy
* for measuring overall memory usage and so on. Overloading also guarantees that
* all memory calls are caught, rather than relying on programmers to always use
* the replacment functions.
*    
*		Whichever approach is taken, further use of macros can provide debugging 
* versions which pass on __FILE__ and __LINE__ values to the memory manager, thus
* providing useful debugging information. The downside to using macros is that
* they are indiscriminate, and will, for example, cause compilation errors if a
* class tries to overload functions of the same name - the method names will just
* get mangled by the macro.
*
*		One solution to this is to have separate header files to turn on
* and off the macros - this works okay if you need to wrap some external code which
* you can (or have to) trust (e.g. DirectX), but not so well if you want to 
* overload things in your own code. However, this is not really that much of a
* limitation since it's just a matter of semantics. For example. if we want a
* different system to cope with the allocation of a class Object (say, using a 
* memory pool), rather than overloading new to have
*
*			Object* obj = new Object; // actually calls a class method
*	
*			delete obj; // actually calls a class method
*	
*		We use the more explicit
*	
*			Object* obj = Object_System::AllocateObject();
*	
*			obj->Release();
*	
*	
*		So, to clarify, Asura does the following
*	
*	
*		1)	new and delete are overloaded by Asura_MemoryManagement_System, which can
*			provide usage data, check for memory leaks, and so on.
*	
*		2)	For a greater degree of debugging info, new and delete are replaced	by
*			macros which report the file and line number which made the call.
*	
*		3)	new and delete CAN NOT be overloaded by any classes.
*	
*			4)	The macros can be turned on or off using "Asura_MemoryManagement_On.h"
*			and "Asura_MemoryManagement_Off.h" to cope with external headers which 
*			break point 3.
*    
*   MH note 13.3.02 -- this is largely based on an article in Game Programming Gems 2
**/

#ifdef ASURA_USE_PRAGMA_ONCE
    #pragma once
#endif

#ifndef __ASURA_MEMORYMANAGEMENT_H__
#define __ASURA_MEMORYMANAGEMENT_H__

enum ASURA_ALIGN_ID
{
	/// Kevin Lea - 11/12/2001 14:32:17 - there is no 'zero-aligned' option;
	/// just use Asura_Allocate.
	
	ASURA_ALIGN_2_BYTE = 2,
	ASURA_ALIGN_16_BIT = 2,

	ASURA_ALIGN_4_BYTE = 4,
	ASURA_ALIGN_32_BIT = 4,
	ASURA_ALIGN_WORD = 4,

	ASURA_ALIGN_8_BYTE = 8,
	ASURA_ALIGN_64_BIT = 8,

	ASURA_ALIGN_16_BYTE = 16,
	ASURA_ALIGN_128_BIT = 16,
	ASURA_ALIGN_QUADWORD = 16,

	ASURA_ALIGN_32_BYTE = 32,

	ASURA_ALIGN_64_BYTE = 64,
	
	ASURA_ALIGN_128_BYTE = 128,
};

// Aligned malloc, if available, must come after the above enum is defined as
// it uses it.
#include "Core/Asura_Platform_MemoryManagement.h"

#include <new>
#include <stddef.h>
#if defined ASURA_PC_SSE
	#include <xmmintrin.h>
#endif

typedef void CheckpointCallback ( int iState );

#ifdef ASURA_MEMORYMANAGEMENT_ENABLED

#if defined(ASURA_MAIN) && !defined(ASURA_MEMORYBANK_ALLOW_STATS) //RAR 28/07/2006 Disable outside of main game/tools since the rendering stuff won't be available
#define ASURA_MEMORYBANK_ALLOW_STATS	1
#endif

enum ASURA_MM_EVENT_ID
{
	ASURA_NEW,
	ASURA_NEWALIGN,
	ASURA_NEWARRAY,
	ASURA_ALLOC,

	ASURA_DELETE,
	ASURA_DELETEALIGN,
	ASURA_DELETEARRAY,
	ASURA_DEALLOC,

	ASURA_NO_OF_MM_EVENTS,
};

#define ASURA_MALLOCDUMPFLAG_FRAMENUM		(0x00000001)
#define ASURA_MALLOCDUMPFLAG_ACTUALSIZE		(0x00000002)
#define ASURA_MALLOCDUMPFLAG_FILEANDLINE	(0x00000004)
#define ASURA_MALLOCDUMPFLAG_RETURNADDR		(0x00000008)
#define ASURA_MALLOCDUMPFLAG_EXTRAINFO		(0x00000010)
#define ASURA_MALLOCDUMPFLAG_ACTUALADDRESS	(0x00000020)
#define ASURA_MALLOCDUMPFLAG_CALLSTACK      (0x00000040) // Only applicable if relevant engine #defines are on.
#define ASURA_MALLOCDUMPFLAG_REPORTEDSIZE   (0x00000080)
#define ASURA_MALLOCDUMPFLAG_REPORTEDADDR   (0x00000100)
#define ASURA_MALLOCDUMPFLAG_ISLEAKREPORT   (0x00000200) // Set when the dump is being written because we've detected memory leaks.

#define ASURA_MALLOCDUMPFLAG_DEFAULTS (ASURA_MALLOCDUMPFLAG_FRAMENUM | ASURA_MALLOCDUMPFLAG_REPORTEDSIZE | ASURA_MALLOCDUMPFLAG_FILEANDLINE | ASURA_MALLOCDUMPFLAG_RETURNADDR | ASURA_MALLOCDUMPFLAG_EXTRAINFO)

class Asura_MemoryBank_Stats;
class Asura_MemoryBank_StatsRender;

// note that this class still exists (in a more minimal form) if ASURA_MEMORYMANAGEMENT_ENABLED is not defined.
class Asura_MemoryManagement_System
{
	public:
		Asura_MemoryManagement_System();

		// KOR - 30/01/2006 - Use this method to check for memory leaks between levels,
		// or wherever you expect the number and size of allocations to be exactly the same.
		// The parameter should be one of ASURA_MM_STATE_TYPES, or a project specific state (see Asura_MemoryManagement_Enums.h)
		// The first time this is called, it will record the number and size of allocations.
		// On subsequent calls, it will compare to the previous call and assert if they have changed.
		static void Checkpoint(int iState);
		// So that platforms can checkpoint their auxiliary memory e.g. Wii's MEM2
		static void SetCheckpointCallback(CheckpointCallback* pfnCallback) { s_pfnCheckpointCallback = pfnCallback; }
        static void DumpMallocInfo( const char* szFilenameNoPath = NULL, const char* szPath = NULL, u_int uFlags = ASURA_MALLOCDUMPFLAG_DEFAULTS );
		static void DumpMallocSummary( const char* szFilenameNoPath = NULL, const char* szPath = NULL );
        static void* Allocate( size_t size, ASURA_MM_EVENT_ID eEventID, u_int uAlignment = 0, const char* szFile = NULL, int iLine = 0, u_int uReturnAddr = 0 );
		static void Deallocate( void* pAddr, ASURA_MM_EVENT_ID eEventID, const char* szFile = NULL, int iLine = 0 );

		//This temporarily disables warnings caused by using the wrong version of delete for the version of new used
		//This has been added to resolve an issue with 'new array' in the nvdia graphics conversion going
		//via a non-array version of new in newaop.cpp when compiled in ResoucePacker.
		//As a result the memory management system incorrectly picks up the type of new being used ,and
		//gives an error when the memory is deleted. 
		//RAR 12/05/2006
		static void SetIgnoreNewAndDeleteTypeMismatch(bool bIgnore);

		// SetMultithreadingEnabled() - Specify whether the game thinks the mutex is required or not.
		// This can be used e.g. if the only time thread safety is needed is for the duration of the 
		// loading screen. However, projects using streaming probably need the mutex all the time.
		// NB. it is up to platform code whether it bothers taking any notice of this flag.
		static void SetMultithreadingEnabled( bool bEnable )	{ s_bMultithreadingEnabled = bEnable; }
		static bool GetMultithreadingEnabled( void )			{ return s_bMultithreadingEnabled; }

		// SetMaxMemToAllocate()/GetMaxMemToAllocate() - set/get max memory that will be allocated
		// via Asura_MemoryManagement_System. This allows consoles to restrict memory usage when 
		// running in devkit modes which have extra memory.
		static void SetMaxMemToAllocate( u_int uMaxBytes );
		static u_int GetMaxMemToAllocate();
		// GetTotalMemAllocated() = how much memory is currently outstanding.
		static u_int GetTotalMemAllocated();

		static void Platform_Initialise(void);
		static void Platform_Lock(void);
		static void Platform_Unlock(void);
		static void Platform_Shutdown(void);
		
		static u_int Platform_GetAddressSpaceStart(void);
		static u_int Platform_GetAddressSpaceEnd(void);
        
#ifdef ASURA_MEMORYMANAGEMENT_PROCESS_ALLOCATED_MEM
        // This will be called immediately after the memory is allocated.
        static void Platform_ProcessAllocatedMem( void* pAddr,
                                                  unsigned uSize,
                                                  ASURA_MM_EVENT_ID eEventID,
                                                  unsigned uAlignment,
                                                  void* pReportedAddr = NULL,
                                                  unsigned uReportedSize = 0 );
#endif

#ifdef ASURA_MEMORYMANAGEMENT_FILL  
        static const unsigned char ucFillValueAllocate; // = 0xCD;
        static const unsigned int  uiFillValueAllocate; // = 0xCDCDCDCD;
        static const unsigned char ucFillValueFree;     // = 0xDD;
        static const unsigned int  uiFillValueFree;     // = 0xDDDDDDDD;

        // Alignment and further arguments are only relevant for allocations.
        static void Platform_FillMem( void* pAddr,
                                      unsigned uSize,
                                      ASURA_MM_EVENT_ID eEventID,
                                      unsigned uAlignment = 0,
                                      void* pReportedAddr = NULL,
                                      unsigned uReportedSize = 0 );

		// Function to allow us to check the guard-bytes from external code. Its way too
		// slow to turn this function on all of the time...
		static void CheckMallocGuardBytes(void);
#endif

        // Called after independent code has finished deallocating some
        // memory.
        static void Platform_PostDeallocate( void );

        static void BeginMallocDump( const char* szFilename );
        static void AddMallocDumpLine( const char* szLine, u_int uLen );
        static void EndMallocDump( void );
		static void FlushMallocDump( void );

		//For disabling memory leak reports during abnormal shutdowns (such as aborting in asserts)
		static void SetReportMemoryLeaksOnShutdown(bool bEnable);

		static const char* GetDefaultMallocDumpPath();
		static const char* GetMemoryLeakFilename();
		
#ifdef ASURA_MEMORYBANK_ALLOW_STATS
		static void StartStatLogging( void )		{ s_bEnableStatLogging = true; }
		static void StopStatLogging( void )			{ s_bEnableStatLogging = false; }
		static void ResetStatLogging( void );
		static void DebugRenderStats( const Asura_MemoryBank_StatsRender& xRender );
		
		static bool s_bEnableStatLogging;
		static bool s_bLogUnspecifiedAllocations;
		static bool s_bRenderTypesWithZeroAllocations;
#endif

		static bool s_bMemoryLeak;//Flag set durring Shutdown() so Platform_Shutdown() can see if there were any leaks
private:
		static CheckpointCallback* s_pfnCheckpointCallback;

		static bool s_bMultithreadingEnabled;
};

void* operator new( size_t size );
void* operator new( size_t size, const std::nothrow_t& ) throw();
void* operator new( size_t size, const char* szFile, int iLine, u_int uReturnAddr );

void* operator new[]( size_t size );
void* operator new[]( size_t size, const std::nothrow_t& ) throw();
void* operator new[]( size_t size, const char* szFile, int iLine, u_int uReturnAddr );

void operator delete( void* pvMemory );
void operator delete( void* pvMemory, const std::nothrow_t& ) throw();
void operator delete( void* pvMemory, const char* szFile, int iLine, u_int uReturnAddr );

void operator delete[]( void* pvMemory );
void operator delete[]( void* pvMemory, const std::nothrow_t& ) throw();
void operator delete[]( void* pvMemory, const char* szFile, int iLine, u_int uReturnAddr );

inline void* Asura_AllocateAlign(unsigned int bytes, enum ASURA_ALIGN_ID alignment)
{
	return Asura_MemoryManagement_System::Allocate(bytes, ASURA_NEWALIGN, alignment);
}

inline void Asura_DeallocateAlign(void* address)
{
	Asura_MemoryManagement_System::Deallocate(address, ASURA_DELETEALIGN);
}

inline void* Asura_AllocateAlign( u_int uNumBytes, ASURA_ALIGN_ID eAlignment, const char* szFile, int iLine, u_int uReturnAddr )
{
	return Asura_MemoryManagement_System::Allocate( uNumBytes, ASURA_NEWALIGN, eAlignment, szFile, iLine, uReturnAddr );
}

inline void Asura_DeallocateAlign( void* pAddr, const char* szFile, int iLine )
{
	Asura_MemoryManagement_System::Deallocate( pAddr, ASURA_DELETEALIGN, szFile, iLine );

}

inline void* Asura_Allocate(unsigned int bytes)
{
	return Asura_MemoryManagement_System::Allocate(bytes, ASURA_ALLOC);
}

inline void Asura_Deallocate(void* address)
{
	Asura_MemoryManagement_System::Deallocate(address, ASURA_DEALLOC);
}

inline void* Asura_Allocate( u_int uNumBytes, const char* szFile, int iLine, u_int uReturnAddr )
{
	return Asura_MemoryManagement_System::Allocate( uNumBytes, ASURA_ALLOC, 0, szFile, iLine, uReturnAddr );
}

inline void Asura_Deallocate( void* pAddr, const char* szFile, int iLine )
{
	Asura_MemoryManagement_System::Deallocate( pAddr, ASURA_DEALLOC, szFile, iLine );
}

// This must define a ASURA_PLATFORM_MEMORYMANAGEMENT_GETRETURNADDR() macro
// which should provide the return address for the point of allocation. The
// type of this must be unsigned int. A value for this can be obtained in
// recent versions of GCC using the __builtin_return_address() function.
#include "Core/Asura_Platform_MemoryManagement_GetReturnAddr.h"

#include "Core/Asura_MemoryManagement_DebugOn.h"

// (MP - 18.09.2002) Restore previous Lint error reporting state.
//lint -restore

#else // not #ifdef ASURA_MEMORYMANAGEMENT_ENABLED

#include <stdlib.h>

/// Kevin Lea - 29/11/2001 11:18:41 - consensus seems to be that it's best not to provide
/// dummy Asura_AllocateAlign methods when the memory manager is disabled. Be warned, if you
/// try to use Asura_AllocateAlign with the manager disabled you will get linking errors.

// (MH - 07.06.2002) PS2 now provides working Asura_AllocateAlign methods. GameCube might
// also be able to (Dreamcast under WindowsCE was able to). It seems only the PC malloc lib
// that doesn't provide aligned malloc...

// Minimal memory management is required even when the full system is
// disabled.
class Asura_MemoryManagement_System
{

    public:

#if ASURA_MEMORYMANAGEMENT_PLATFORM_INIT_WHEN_DISABLED
        Asura_MemoryManagement_System( void );

        static void Platform_Initialise( void );
        static void Platform_Shutdown( void );
#endif

        static void Platform_Lock( void );
        static void Platform_Unlock( void );

		// KOR - 30/01/2006 - Dummy implementation of Checkpoint
		static inline void Checkpoint(int iState) {}
		static void SetCheckpointCallback(CheckpointCallback* pfnCallback) {}
		static void SetIgnoreNewAndDeleteTypeMismatch(bool bIgnore){};

		// SetMultithreadingEnabled() - Specify whether the game thinks the mutex is required or not.
		// This can be used e.g. if the only time thread safety is needed is for the duration of the 
		// loading screen. However, projects using streaming probably need the mutex all the time.
		// NB. it is up to platform code whether it bothers taking any notice of this flag.
		static void SetMultithreadingEnabled( bool bEnable )	{ s_bMultithreadingEnabled = bEnable; }
		static bool GetMultithreadingEnabled( void )			{ return s_bMultithreadingEnabled; }

private:

		static bool s_bMultithreadingEnabled;
};

#if defined ASURA_PC_SSE
inline void* Asura_Allocate(unsigned int bytes)
{
    return _mm_malloc(bytes,sizeof (__m128));
}

inline void Asura_Deallocate(void* address)
{
	_mm_free(address);
}

// DLH - 20 April 2006 15:15:10 - Had to add these to make sure that new and delete's where align
inline void* operator new(size_t size)
{
	return Asura_Allocate( static_cast<u_int>(size));
}

inline void* operator new[](size_t size)
{
	return Asura_Allocate( static_cast<u_int>(size));
} 

inline void operator delete( void *address )
{
	Asura_Deallocate( address);
}

inline void operator delete[]( void *address )
{	
	Asura_Deallocate( address);
}

#else
void* Asura_Allocate(unsigned int bytes);

inline void Asura_Deallocate(void* address)
{
    free (address);
}
#endif

#endif //#else not #ifdef ASURA_MEMORYMANAGEMENT_ENABLED

#if !defined(ASURA_MEMORYMANAGEMENT_INIT_OBJ_IN_PLATFORM_CODE) && (defined(ASURA_MEMORYMANAGEMENT_ENABLED) || ASURA_MEMORYMANAGEMENT_PLATFORM_INIT_WHEN_DISABLED)
// MH 14.3.02 -- there is one of these included per file to force the constructor
// to be called before any other global objects. If the memory manager hasn't been 
// initialised yet the constructor does that. The objects are not used again.
static Asura_MemoryManagement_System Asura_MMS_InitialiserObject;
#endif

/**
 * This module is about allocating ''utility'' memory, that is memory
 * that is not for game data, but for some kind of metadata, usually
 * debug metadata, for example the ''nodes'' used in debug mode for
 * 'Asura_MemoryManagement_Core'.
 *
 * The memory used for ''utility'' allocations is usually drawn from
 * some special arena, so as not to disturb the statistics for game
 * related allocations.
 *
 * The API below is deliberately similar to that of
 * 'Asura_MemoryManagement_System', if nothing else for familiarity.
 */
#if defined ASURA_MEMORYMANAGEMENT_ENABLED

#if defined __cplusplus
class Asura_MemoryUtility
{
public:
	/**
	 * Independent initialisation.
	 */
	static void Initialise(void);
	/**
	 * Independent termination.
	 */
	static void Shutdown(void);

	/**
	 * Allocate a block of ''utility'', not game, memory,
	 * usually for debugging purposes.
	 */
	static void* Allocate(size_t size);
	/**
	 * Free a block of ''utility'', not game, memory,
	 * usually for debugging purposes.
	 */
	static void Deallocate(void* pAddr);

protected:
	/**
	 * Platform dependent initialisation.
	 */
	static void Platform_Initialise(void);
	/**
	 *Platform dependent termination.
	 */
	static void Platform_Shutdown(void);

	/**
	 * Platform dependent function called before allocating a block
	 * of ''utility'' memory: if it returns non-0 use that.
	 */
	static void* Platform_PreAllocate(size_t size);
	/**
	 * Platform dependent function called after allocating a block
	 * of ''utility'' memory: called only if 'Platform_PreAllocate'
	 * returned '0', and after the platform independent allocator.
	 *
	 * Usually its purpose is statistics gathering and checking.
	 */
	static void* Platform_PostAllocate(void* pAddr);

	/**
	 * Platform dependent pre-deallocation function for ''utility''
	 * memory: if it returns 'true' the deallocation is done.
	 */
	static bool Platform_PreDeallocate(void* pAddr);
	/**
	 * Platform dependent post-deallocation function for ''utility''
	 * memory: called only if 'Platform_PreDeallocate' returned
	 * 'false', and after the platform independent deallocator.
	 *
	 * Usually its purpose is statistics gathering and checking.
	 */
	static void Platform_PostDeallocate(void* pAddr);

private:
	/* This module should have no instances */
	Asura_MemoryUtility() {}
};
#endif

#if defined __cplusplus
extern "C" {
#endif

/**
 * Create a block of utility memory, from C.
 *
 * Wrapper for the C++ function to 'Asura_MemoryUtility::Allocate'.
 */
void* Asura_C_UtilityAllocate(size_t uNumBytes);


/** Delete a block of utility memory, from C.
 *
 * Wrapper for the C++ function to 'Asura_MemoryUtility::Deallocate'.
 */
void Asura_C_UtilityDeallocate(void* pAddr);

#if defined __cplusplus
}
#endif

#endif

#endif//__ASURA_MEMORYMANAGEMENT_H__
