// Copyright (c) 2007-2009 Nokia Corporation and/or its subsidiary(-ies).
// All rights reserved.
// This component and the accompanying materials are made available
// under the terms of "Eclipse Public License v1.0"
// which accompanies this distribution, and is available
// at the URL "http://www.eclipse.org/legal/epl-v10.html".
//
// Initial Contributors:
// Nokia Corporation - initial contribution.
//
// Contributors:
//
// Description:
//

#include "WsMemMgr.h"
#include "inifile.h"
#include "panics.h"
#include "wstop.h"

static const TTimeIntervalMicroSeconds KBurstDuration = 1000000; //one second
static const TInt KMaxMemoryReleasesPerBurst = 5;
CWsMemoryManager * CWsMemoryManager::iStatic = NULL;

CWsMemoryManager * CWsMemoryManager::Static()
	{
	return iStatic;
	}

CWsMemoryManager * CWsMemoryManager::NewLC()
	{
	CWsMemoryManager * self = new (ELeave) CWsMemoryManager;
	CleanupStack::PushL(self);
	self->ConstructL();
	iStatic = self;
	return iStatic;
	}

CWsMemoryManager::CWsMemoryManager()
	{
	iImpl = User::SwitchAllocator(this);
	}
	
CWsMemoryManager::~CWsMemoryManager()
	{
	WS_ASSERT_ALWAYS(this == User::SwitchAllocator(iImpl),EWsPanicMemoryManager);
	iStatic = 0;
	if (iReserve!=NULL)
		{
		Free(iReserve);
		}
	}

void CWsMemoryManager::ConstructL()
	{
	_LIT(KMemMgrReserve, "MEMORYRESERVE");
	const TInt KDefaultMemMgrReserve = 1024;
	
	if (!WsIniFile->FindVar(KMemMgrReserve, iReserveSize))
		iReserveSize = KDefaultMemMgrReserve;

	if (iReserveSize > 0)
		iReserve = Alloc(iReserveSize);
	
	iCurrentBurstStart.UniversalTime();
	}

/**
Tells the memory manager to fail on next retry.
I.e. whenever the next allocation failure occurs, the memory manager won't try 
to free up memory to have another go at the allocation.

N.B. this only applies for the next failure, the state is reset when there is an
allocation failure.

This method is only to be used for OOM testing.
*/
void CWsMemoryManager::SetFailNextRetry()
	{
	iFailNextRetry = ETrue;
	}

/**
Implementing RAllocator
*/

/**
Alloc and ReAlloc attempt to obtain memory through CWsTop::ReleaseMemory when they run low.
ReleaseMemory looks for blocks of memory that the window server doesn't need urgently and frees
them.
*/
TAny* CWsMemoryManager::Alloc(TInt aSize)
	{
	TBool keepTrying = ETrue;
	do
		{
		if(iReleasing)
			return iImpl->Alloc(aSize); //fallback on RAllocator
		
		if(TAny* ret = iImpl->Alloc(aSize)) //normal case
			return ret;
		
		TTime now;
		now.UniversalTime();
		if(now.MicroSecondsFrom(iCurrentBurstStart) < KBurstDuration)
			{
			iCurrentBurstReleaseCount++;
			if(iCurrentBurstReleaseCount > KMaxMemoryReleasesPerBurst)
				return NULL;
			}
		else
			{
			iCurrentBurstStart = now;
			iCurrentBurstReleaseCount = 1;
			}
		
		if(iReserveEnabled && iReserve && (aSize < iReserveSize))
			{
			Free(iReserve);
			iReserve = NULL;
			}
		else
			{
			iReleasing = ETrue;
			keepTrying = CWsTop::ReleaseMemory();
			if(keepTrying)
 				{
 				const TInt reclaimed = Compress(); //Try to give back to the OS
 				}
			iReleasing = EFalse;
			}
		
		//used for OOM testing only
		if(iFailNextRetry)
			{
			iFailNextRetry = EFalse;
			keepTrying = EFalse;
			}
		
		} while(keepTrying);

	return NULL;
	}

TAny* CWsMemoryManager::ReAlloc(TAny* aPtr, TInt aSize, TInt aMode)
	{
	TBool keepTrying = ETrue;
	do
		{
		if(iReleasing)
			return iImpl->ReAlloc(aPtr, aSize, aMode); //fallback on RAllocator
		
		if(TAny* ret = iImpl->ReAlloc(aPtr, aSize, aMode)) //normal case
			return ret;
			
		TTime now;
		now.UniversalTime();
		if(now.MicroSecondsFrom(iCurrentBurstStart) < KBurstDuration)
			{
			iCurrentBurstReleaseCount++;
			if(iCurrentBurstReleaseCount > KMaxMemoryReleasesPerBurst)
				return NULL;
			}
		else
			{
			iCurrentBurstStart = now;
			iCurrentBurstReleaseCount = 1;
			}
			
		if(iReserveEnabled && iReserve && (aSize < iReserveSize))
			{
			Free(iReserve);
			iReserve = NULL;
			}
		else
			{
			iReleasing = ETrue;
			keepTrying = CWsTop::ReleaseMemory();
			if(keepTrying)
 				{
 				const TInt reclaimed = Compress(); //Try to give back to the OS
 				}	
			iReleasing = EFalse;
			}
			
		//used for OOM testing only
		if(iFailNextRetry)
			{
			iFailNextRetry = EFalse;
			keepTrying = EFalse;
			}

		} while(keepTrying);

	return NULL;
	}

/**
The rest of these functions just call the default implementation
*/
void CWsMemoryManager::Free(TAny* aPtr)
	{
	return iImpl->Free(aPtr);
	}

TInt CWsMemoryManager::AllocLen(const TAny* aCell) const
	{
	return iImpl->AllocLen(aCell);
	}

TInt CWsMemoryManager::Compress()
	{
	return iImpl->Compress();
	}

void CWsMemoryManager::Reset()
	{
	iImpl->Reset();
	}

TInt CWsMemoryManager::AllocSize(TInt& aTotalAllocSize) const
	{
	return iImpl->AllocSize(aTotalAllocSize);
	}

TInt CWsMemoryManager::Available(TInt& aBiggestBlock) const
	{
	return iImpl->Available(aBiggestBlock);
	}

TInt CWsMemoryManager::DebugFunction(TInt aFunc, TAny* a1, TAny* a2)
	{
	return iImpl->DebugFunction(aFunc,a1,a2);
	}

TInt CWsMemoryManager::Count() const
	{
	return iImpl->Count();
	}
/** This is a fairly dumb way to enable and disable the reserve, but we normally
get away with it because wserv is high priority.  A better approach would be to
use placement new into the reserve memory and manage it directly.  This would also
allow us to track misbehaving code which allocated during OOM drawing and didn't
free at the end.
*/
void CWsMemoryManager::EnableReserve()
	{
	WS_ASSERT_DEBUG(!iReserveEnabled, EWsPanicMemoryManager);
	iReserveEnabled = ETrue;
	}

void CWsMemoryManager::DisableReserve()
	{
	WS_ASSERT_DEBUG(iReserveEnabled, EWsPanicMemoryManager);
	iReserveEnabled = EFalse;
	if((!iReserve) && (iReserveSize > 0))
		iReserve = Alloc(iReserveSize);
	}
