
///////////////////////////////////////////////////////////////////////////////
//
// File   : Asura_Streaming_ZoneHandler.cpp
// Author : Andy Tate
// Date   : 19-Apr-2006
//
// Copyright (C) 2006 Rebellion, All Rights Reserved.
//
///////////////////////////////////////////////////////////////////////////////

// RJT NOTE: Required for GCC builds as the file list generation doesn't account for VC project exclusion from build
#ifdef ASURA_USE_STREAMING

////////////////////////////////////////////////////////////////////////////////
// Include files...
////////////////////////////////////////////////////////////////////////////////

#include "Core/Asura.h"

// This file's header...
#include "Streaming/Asura_Streaming_ZoneHandler.h"

// Asura independent header-files...
#include "Core/Asura_TextureManagement.h"
#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
#include "Core/Memory/Asura_MemoryBank.h"
#include "Core/Memory/Asura_ResourceAllocator.h"
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
#include "Core/Handlers/Asura_Handler_System.h"
#include "Streaming/Asura_Streaming_ZoneDataState.h"

// Asura platform header-files...

// Game independent header-files...

// Game platform header-files...

// Tools header-files...


////////////////////////////////////////////////////////////////////////////////
// Constant declarations...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Static variable declarations...
////////////////////////////////////////////////////////////////////////////////

// Handler used during callback functions...
Asura_Streaming_ZoneHandler*	Asura_Streaming_ZoneHandler::s_pxCallbackHandler			= NULL;
Asura_Streaming_Zone*			Asura_Streaming_ZoneHandler::s_pxCurrentZoneBeforeCallback	= NULL;
const Asura_Vector_3*			Asura_Streaming_ZoneHandler::s_pxCurrentPosition			= NULL;
bool							Asura_Streaming_ZoneHandler::s_bCallbackBlocking			= true;


////////////////////////////////////////////////////////////////////////////////
// Private functions...
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::GetFreeZoneIndex
// Author       : Andy Tate
// Date         : 31-Mar-2006
//
// Description  : Attempts to find the first free zone from the specified
//				  list of currently used zones.
//
// Parameters   : The data-state to find a free zone for.
//
// Return value : The index of the next free zone.
////////////////////////////////////////////////////////////////////////////////

u_int Asura_Streaming_ZoneHandler::GetFreeZoneIndex (const Asura_Streaming_Zone::DataState eDataState) const
{
	const u_int	uMaxZones	= m_auMaxDataIndices[eDataState];
	const u_int	uZonesUsed	= m_auZonesUsed[eDataState];
	u_int		uMask		= 1;

	// Look for the first unused zone...
	for(u_int u = 0; u < uMaxZones; u++, uMask <<= 1)
	{
		// Is this zone free?
		if((uZonesUsed & uMask) == 0)
		{
			return(u);
		}
	}

	// No free zones - that's bad...
	return(uMaxZones);
}

/// End of function 'Asura_Streaming_ZoneHandler::GetFreeZoneIndex'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::FindZoneHashUsingFilename
// Author       : Andy Tate
// Date         : 19-May-2006
////////////////////////////////////////////////////////////////////////////////

Asura_Hash_ID Asura_Streaming_ZoneHandler::FindZoneHashUsingFilename (const char* szFilename) const
{
	Asura_String	xFullPath(szFilename);
	Asura_String	xFilename;

	// Split-up the filename...
	if(xFullPath.SplitFilename(NULL, &xFilename, NULL))
	{
		const Asura_Hash_ID	uHash	= Asura_Streaming_Zone::Project_CalculateHashForZoneName(xFilename);

		// Search for this filename...
		return(uHash);
	}
	else
	{
		Asura_Assert(false, "Failed to split filename.");
		return(ASURA_HASH_ID_UNSET);
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::FindZoneUsingFilename'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::SetCurrentZoneDataState
// Author       : Andy Tate
// Date         : 23-May-2006
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::SetCurrentZoneDataState (Asura_Streaming_ZoneDataState& xDataState,
														   const bool bAllowDeletion)
{
	// Set the sub-system handlers...
	if(!xDataState.IsDataIndexValid())
	{
		Asura_Handler_System::SetAllZoneData(ASURA_HASH_ID_UNSET, 0, false);
	}
	else
	{
#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
		// Do we have a data-state?
		if(xDataState.IsReadyForUse())
		{
			const u_int			uDataIndex		= xDataState.GetDataIndex();
			Asura_MemoryBank*	pxMemoryBank	= GetMemoryBankFromDataIndex(uDataIndex);

			// Ensure we're using the correct memory-bank...
			Asura_ResourceAllocator::SetCurrentMemoryBank(pxMemoryBank);
			pxMemoryBank->SetAllowDeletion(bAllowDeletion);
		}
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)

		// Set the sub-systems correctly...
		xDataState.SetSubsystemHandlers();
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::SetCurrentZoneDataState'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::DestroyData
// Author       : Andy Tate
// Date         : 3-Apr-2006
//
// Description  : Destroys data held by a the specified streaming zone.
//
// Parameters   : A reference to teh zone to have its data destroyed.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::DestroyData (Asura_Streaming_Zone& xZone)
{
	// Destroy the appropriate data-states...
	for(u_int u = 0; u < Asura_Streaming_Zone::MAX_DATA_STATES; u++)
	{
		if(m_auMaxDataIndices[u] > 0)
		{
			const Asura_Streaming_Zone::DataState	eDataState	= static_cast<Asura_Streaming_Zone::DataState>(u);

			// Time to begin...
			CommenceDestruction(eDataState, xZone, true);
		}
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::DestroyData'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::Update
// Author       : Andy Tate
// Date         : 6-Apr-2006
//
// Description  : Updates the state of the specified zone and decide whether 
//				  this means that we need to load or destroy the data held by
//				  this zone.
//
// Parameters   : A reference to the zone to be updated and a pointer to the 
//				  current zone before ANY updates were performed.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::Update (Asura_Streaming_Zone& xZoneToUpdate,
										  const Asura_Streaming_Zone* pxCurrentZone,
										  const bool bBlocking)
{
	const Asura_Hash_ID	uHash	= xZoneToUpdate.GetHashID();

	// Is this zone to be deleted?
	if(xZoneToUpdate.IsDataUnused())
	{
		m_xZoneHashTree.DeleteNodeByHash(uHash);
	}
	else
	{
		const Asura_Hash_ID	uCurrentZoneHash	= (pxCurrentZone == NULL)
												? ASURA_HASH_ID_UNSET
												: pxCurrentZone->GetHashID();
		const bool			bIsCurrent			= (uCurrentZoneHash == uHash);

		// Update the data-states...
		for (int i = (Asura_Streaming_Zone::MAX_DATA_STATES - 1); i >= 0; i--)
		{
			const Asura_Streaming_Zone::DataState	eDataState				= static_cast<Asura_Streaming_Zone::DataState>(i);
			Asura_Streaming_ZoneDataState*			pxDataState				= xZoneToUpdate.GetZoneDataState(eDataState);
			const bool								bIsConnectionAvailable	= (pxCurrentZone == NULL)
																			? false
																			: pxCurrentZone->IsConnectionAvailable(uHash, eDataState, s_pxCurrentPosition);

			// Have we finished with this zone?
			if(xZoneToUpdate.UpdateState(eDataState, bBlocking))
			{
				// No  - what do we need to do?
				if(pxDataState->IsNotPresent())
				{
					// Nothing to be done...
				}
				else if(pxDataState->IsTimeToLoad())
				{
					CommenceLoad(eDataState, xZoneToUpdate, bBlocking);
				}
				else if(pxDataState->IsTimeToDestroy())
				{
					CommenceDestruction(eDataState, xZoneToUpdate, bBlocking);
				}
				else
				{
					if(bIsConnectionAvailable)
					{
						pxDataState->RequestLoad();
					}
					else if(!bIsCurrent)
					{
#if !defined(ASURA_TOOLS)
						//Don't want to be triggering the destruction of zones because they are no longer connected
						//while in tools. (Or at any rate, don't want that happening for now...)
						//RAR 11/04/2006
						pxDataState->RequestDestruction();
#endif	// !defined(ASURA_TOOLS)
					}
				}
			}
			else
			{
				pxDataState	= xZoneToUpdate.GetZoneDataState(eDataState);

				if(pxDataState != NULL)
				{
					if(pxDataState->IsNotPresent())
					{
						// Nothing to be done...
					}
					else if(bIsConnectionAvailable)
					{
						pxDataState->RequestLoad();
					}
					else
					{
						xZoneToUpdate.DestroyDataState(eDataState, bBlocking);
					}
				}
			}
		}

		// Update the global data present flags...
		m_bAllZonesFull		= m_bAllZonesFull && xZoneToUpdate.IsDataFull();
		m_bAllZonesEmpty	= m_bAllZonesEmpty && xZoneToUpdate.IsDataEmpty();
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::Update'. ///



////////////////////////////////////////////////////////////////////////////////
// Protected functions...
////////////////////////////////////////////////////////////////////////////////


////////////////////////////////////////////////////////////////////////////////
// Public functions...
////////////////////////////////////////////////////////////////////////////////

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::Asura_Streaming_ZoneHandler
// Author       : Andy Tate
// Date         : 19-Apr-2006
////////////////////////////////////////////////////////////////////////////////

Asura_Streaming_ZoneHandler::Asura_Streaming_ZoneHandler (const u_int uFirstUniqueIndex,
														  const bool bEveryZoneCreatedIsUnique,
														  const Asura_Hash_ID uParentHandlerHash,
														  const u_int* puNumDataIndices,
														  const u_int* puFirstDataIndex)
: m_uParentHandlerHash(uParentHandlerHash)

, m_uFirstUniqueIndex(uFirstUniqueIndex)

, m_bAllZonesFull(false)
, m_bAllZonesEmpty(true)

, m_pxCurrentZone(NULL)
{
	// Initialise the data-states...
	for(u_int u = 0; u < Asura_Streaming_Zone::MAX_DATA_STATES; u++)
	{
		m_auFirstDataIndex[u]		= puFirstDataIndex[u];
		m_auMaxDataIndices[u]		= puNumDataIndices[u];
		m_auZonesUsed[u]			= 0;

#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
		m_appxZoneMemoryBanks[u]	= NULL;
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
	}

	// Do we need a unique-index tree?
	m_pxUniqueIndexHashTree	= (bEveryZoneCreatedIsUnique)
							? new Asura_HashTree
							: NULL;
}

/// End of function 'Asura_Streaming_ZoneHandler::Asura_Streaming_ZoneHandler'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::~Asura_Streaming_ZoneHandler
// Author       : Andy Tate
// Date         : 19-Apr-2006
////////////////////////////////////////////////////////////////////////////////

Asura_Streaming_ZoneHandler::~Asura_Streaming_ZoneHandler ()
{
	// Remove the unique index tree (if we have one)...
	delete(m_pxUniqueIndexHashTree);

#if defined(ASURA_ASSERT)
	u_int	uNumZonesUsed	= 0;

	// Validation...
	for(u_int u = 0; u < Asura_Streaming_Zone::MAX_DATA_STATES; u++)
	{
		uNumZonesUsed	+= m_auZonesUsed[u];
	}

	Asura_Assert((uNumZonesUsed == 0), "Zone shutdown unsuccessful.");
#endif	/// defined(ASURA_ASSERT)

#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
	// Destroy the memory-banks..
	DeallocateMemoryBanks();
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
}

/// End of function 'Asura_Streaming_ZoneHandler::~Asura_Streaming_ZoneHandler'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::CreateZone
// Author       : Andy Tate
// Date         : 2-Feb-2006
//
// Description  : Creates a streaming zone of the specified filename.
//
// Parameters   : Pointers to the name and filename we're to use for this zone.
//
// Return value : A pointer to the zone created, NULL if we can't create the
//				  zone for some reason.
////////////////////////////////////////////////////////////////////////////////

Asura_Streaming_Zone* Asura_Streaming_ZoneHandler::CreateZone (const char* szFilename,
															   const bool bAllowEnvironment)
{
	const Asura_Hash_ID		uHash	= FindZoneHashUsingFilename(szFilename);
	Asura_Streaming_Zone*	pxZone	= m_xZoneHashTree.GetDataByHash(uHash);

	// Does this zone already exist?
	if(pxZone == NULL)
	{
		// No - time to create one...
		pxZone	= m_xZoneHashTree.AddNode(uHash);

		// Do we have to store unique indices?
		if(m_pxUniqueIndexHashTree != NULL)
		{
			int	iUniqueIndex;

			// Yes - ensure that this index is in our hash-tree...
			m_pxUniqueIndexHashTree->FindNodeIndexByHash(uHash, true, iUniqueIndex);
		}

		// ...and initialise it...
		pxZone->Initialise(*this, szFilename, uHash, bAllowEnvironment);
		return(pxZone);
	}
	else
	{
		// Yes - return failure...
		Asura_Assert(false, "Zone already present in this zone-tree.");
		return(NULL);
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::CreateZone'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::AllocateFreeZoneIndex
// Author       : Andy Tate
// Date         : 11-May-2006
////////////////////////////////////////////////////////////////////////////////

bool Asura_Streaming_ZoneHandler::AllocateFreeZoneIndex (Asura_Streaming_ZoneDataState& xZoneDataState)
{
	const Asura_Streaming_Zone&				xZone		= xZoneDataState.GetZone();
	const Asura_Streaming_Zone::DataState	eDataState	= xZone.GetDataState(xZoneDataState);
	const u_int								uZoneIndex	= GetFreeZoneIndex(eDataState);

	// Is our zone-index valid?
	if(uZoneIndex < m_auMaxDataIndices[eDataState])
	{
		const u_int			uDataIndex		= m_auFirstDataIndex[eDataState] + uZoneIndex;
#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
		Asura_MemoryBank*	pxMemoryBank	= GetMemoryBankFromDataIndex(uDataIndex);

		// Ensure we're using the correct memory-bank...
		Asura_ResourceAllocator::SetCurrentMemoryBank(pxMemoryBank);
		pxMemoryBank->Reset();
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)

		// Mark the zone we're using...
		m_auZonesUsed[eDataState]	|= (1 << uZoneIndex);
		xZoneDataState.SetDataIndex(uDataIndex);
		return(true);
	}
	else
	{
		return(false);
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::AllocateFreeZoneIndex'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::DeallocateFreeZoneIndex
// Author       : Andy Tate
// Date         : 11-May-2006
////////////////////////////////////////////////////////////////////////////////

bool Asura_Streaming_ZoneHandler::DeallocateFreeZoneIndex (Asura_Streaming_ZoneDataState& xZoneDataState)
{
	const Asura_Streaming_Zone&				xZone		= xZoneDataState.GetZone();
	const Asura_Streaming_Zone::DataState	eDataState	= xZone.GetDataState(xZoneDataState);
	const u_int								uDataIndex	= xZoneDataState.GetDataIndex();
	const u_int								uZoneIndex	= uDataIndex - m_auFirstDataIndex[eDataState];

	// Remove the data held by the high-detail zone...
	m_auZonesUsed[eDataState]	&= ~(1 << uZoneIndex);
	xZoneDataState.ResetDataIndex();
	return(true);
}

/// End of function 'Asura_Streaming_ZoneHandler::DeallocateFreeZoneIndex'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::CommenceEmptyCreation
// Author       : Andy Tate
// Date         : 19-Apr-2006
//
// Description  : Begins creating an empty data-state for the specified zone.
//
// Parameters   : The data-state to perform the creation for and a reference to the
//				  zone to actually be created.
//
// Return value : "true" if creation is commenced, "false" if not.
////////////////////////////////////////////////////////////////////////////////

bool Asura_Streaming_ZoneHandler::CommenceEmptyCreation (const Asura_Streaming_Zone::DataState eDataState,
														 Asura_Streaming_Zone& xZone,
														 const bool bBlocking)
{
	Asura_Streaming_ZoneDataState*	pxDataState	= xZone.GetZoneDataState(eDataState);

	// Do we need to create a data-state?
	if(pxDataState == NULL)
	{
		pxDataState	= xZone.CreateDataState(eDataState);

		// Do we have a data-state?
		if(pxDataState == NULL)
		{
			return(false);
		}
		else
		{
			// Begin creation...
			if(bBlocking)
			{
				SetCurrentZone(xZone.GetHashID(), eDataState, false);
			}
			return(pxDataState->CommenceDataEmptyCreation(m_uParentHandlerHash, bBlocking));
		}
	}
	else
	{
		// Begin creation...
		if(bBlocking)
		{
			SetCurrentZone(xZone.GetHashID(), eDataState, false);
		}
		return(pxDataState->CommenceDataEmptyCreation(m_uParentHandlerHash, bBlocking));
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::CommenceEmptyCreation'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::CommenceLoad
// Author       : Andy Tate
// Date         : 19-Apr-2006
//
// Description  : Begins loading the specified data-state for the specifid zone.
//
// Parameters   : The data-state to perform the load for and a reference to the
//				  zone to actually be loaded.
//
// Return value : "true" if loading is commenced, "false" if not.
////////////////////////////////////////////////////////////////////////////////

bool Asura_Streaming_ZoneHandler::CommenceLoad (const Asura_Streaming_Zone::DataState eDataState,
												Asura_Streaming_Zone& xZone,
												const bool bBlocking)
{
	Asura_Streaming_ZoneDataState*	pxDataState	= xZone.GetZoneDataState(eDataState);

	// Do we need to create a data-state?
	if(pxDataState == NULL)
	{
		pxDataState	= xZone.CreateDataState(eDataState);

		// Do we have a data-state?
		if(pxDataState == NULL)
		{
			return(false);
		}
		else if(pxDataState->IsNotPresent())
		{
			return(false);
		}
		else
		{
			// Begin loading...
			if(bBlocking)
			{
				SetCurrentZone(xZone.GetHashID(), eDataState, false);
			}
			return(pxDataState->CommenceDataLoad(m_uParentHandlerHash, bBlocking));
		}
	}
	else if(pxDataState->IsNotPresent())
	{
		return(false);
	}
	else
	{
		// Begin loading...
		if(bBlocking)
		{
			SetCurrentZone(xZone.GetHashID(), eDataState, false);
		}
		return(pxDataState->CommenceDataLoad(m_uParentHandlerHash, bBlocking));
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::CommenceLoad'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::CommenceDestruction
// Author       : Andy Tate
// Date         : 7-Apr-2006
//
// Description  : Handles the destruction of the specified data-state.
//
// Parameters   : A reference to the zone to use, the data-state to destroy within
//				  this zone, a reference to the used-zone bits to be filled in and
//				  the maximum number of zones allowed for this data-state.
//
// Return value : "true" if successfully destroyed, false if not.
////////////////////////////////////////////////////////////////////////////////

bool Asura_Streaming_ZoneHandler::CommenceDestruction (const Asura_Streaming_Zone::DataState eDataState,
													   Asura_Streaming_Zone& xZone,
													   const bool bBlocking)
{
	Asura_Streaming_ZoneDataState*	pxDataState	= xZone.GetZoneDataState(eDataState);

	// Do we have a data-state?
	if(pxDataState == NULL)
	{
		return(false);
	}
	else if(false)	//pxDataState->IsEmpty() || pxDataState->IsNotPresent() || pxDataState->IsUnused())
	{
		return(true);
	}
	else
	{
		// Remove the data held by the high-detail zone...
		if(bBlocking)
		{
			SetCurrentZone(xZone.GetHashID(), eDataState, true);
			xZone.DestroyDataState(eDataState, bBlocking);
		}
		else
		{
#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
			Asura_MemoryBank* const pxOldBank = Asura_ResourceAllocator::GetCurrentMemoryBank();
			if (pxDataState->IsDataIndexValid() && pxDataState->IsReadyForUse())
			{
				const u_int			uDataIndex		= pxDataState->GetDataIndex();
				Asura_MemoryBank*	pxMemoryBank	= GetMemoryBankFromDataIndex(uDataIndex);

				// Ensure we're using the correct memory-bank...
				if (pxMemoryBank)
				{
					Asura_ResourceAllocator::SetCurrentMemoryBank(pxMemoryBank);
					pxMemoryBank->SetAllowDeletion(true);
				}
			}
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)

			pxDataState->SetSubsystemHandlers();
			xZone.DestroyDataState(eDataState, bBlocking);

#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
			// Restore the old memory bank.
			Asura_ResourceAllocator::SetCurrentMemoryBank(pxOldBank);
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
		}

		return(true);
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::CommenceDestruction'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::DestroyZone
// Author       : Andy Tate
// Date         : 2-Feb-2006
//
// Description  : Handles the destruction of the specified zone.
//
// Parameters   : The hash-ID for the zone to destroy.
//
// Return value : "true" if destroyed successfully, "false" if failed.
////////////////////////////////////////////////////////////////////////////////

bool Asura_Streaming_ZoneHandler::DestroyZone (const Asura_Hash_ID uHash)
{
	Asura_Streaming_ZoneHandler*	pxOldHandler	= s_pxCallbackHandler;
	const Asura_Hash_ID				uOldZoneHash	= (m_pxCurrentZone == NULL)
													? ASURA_HASH_ID_UNSET
													: m_pxCurrentZone->GetHashID();
	Asura_Streaming_Zone*			pxZone			= m_xZoneHashTree.GetDataByHash(uHash);

	// Store the callback handler...
	s_pxCallbackHandler	= this;

	// Destroy the data...
	DestroyDataZoneCallback(uHash, pxZone);

	// Restore the old data...
	s_pxCallbackHandler	= pxOldHandler;
	SetCurrentZone((uOldZoneHash == uHash) ? ASURA_HASH_ID_UNSET : uOldZoneHash);

	// ...before deleting this node...
	return(m_xZoneHashTree.DeleteNodeByHash(uHash));
}

/// End of function 'Asura_Streaming_ZoneHandler::DestroyZone'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::DestroyAllZones
// Author       : Andy Tate
// Date         : 20-Mar-2006
//
// Description  : Loops through ALL of the high and low-detail zones and destroys
//				  them.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::DestroyAllZones (void)
{
	// Do we have any data to destroy?
	if(m_xZoneHashTree.GetNumberOfNodes() > 0)
	{
		Asura_Streaming_ZoneHandler*	pxOldHandler	= s_pxCallbackHandler;

		// Yes - destroy the data for each zone...
		s_pxCallbackHandler				= this;
		s_pxCurrentZoneBeforeCallback	= m_pxCurrentZone;

		m_xZoneHashTree.Traverse(DestroyDataZoneCallback);

		s_pxCallbackHandler	= pxOldHandler;

		// Wait for destruction to be completed...
		do
		{
			Update(NULL, true);

		} while(!m_bAllZonesEmpty);

		// Remove the zones themselves...
		m_xZoneHashTree.Reset();
		ResetCurrentZone();
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::DestroyAllZones'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::ReloadZone
// Author       : Andy Tate
// Date         : 26-May-2006
////////////////////////////////////////////////////////////////////////////////

bool Asura_Streaming_ZoneHandler::ReloadZone (const Asura_Streaming_Zone::DataState eDataState,
											  Asura_Streaming_Zone& xZone,
											  const bool bBlocking)
{
	// Destroy the old data...
	if(CommenceDestruction(eDataState, xZone, bBlocking))
	{
		Asura_Streaming_ZoneDataState*	pxDataState	= xZone.CreateDataState(eDataState);

		// ...before loading the new data...
		if(pxDataState == NULL)
		{
			Asura_Assert(false, "Failed to create data-state when reloading zone.");
			return(false);
		}
		else if((eDataState != Asura_Streaming_Zone::HIGH_DETAIL_DATA) && pxDataState->IsNotPresent())
		{
			return(true);
		}
		else if(CommenceLoad(eDataState, xZone, bBlocking))
		{
			return(true);
		}
		else
		{
			Asura_Assert(false, "Failed to commence loading when reloading zone.");
			return(false);
		}
	}
	else
	{
		Asura_Assert(false, "Failed to commence destruction when reloading zone.");
		return(false);
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::ReloadZone'. ///


////////////////////////////////////////////////////////////////////////////////
// Function		: Asura_Streaming_ZoneHandler::AddToUniqueIndexHashTree
// Author		: M.G.Harris
// Date			: 15-Nov-2006
//
// Parameters	: uHash (hash value of stripped filename for zone)
//
// Description:
//  Function to allow projects to pre-fill the unique index hash tree.
//  This allows them to ensure that zones always have the same unique index
//  (which is used to modify GUIDs). Hence potentially allowing such things as
//  messaging between zones.
//
////////////////////////////////////////////////////////////////////////////////
void Asura_Streaming_ZoneHandler::AddToUniqueIndexHashTree(const Asura_Hash_ID uHash)
{
	if (m_pxUniqueIndexHashTree)
	{
		// Add the hash to the tree.
		int	iUniqueIndex;
		m_pxUniqueIndexHashTree->FindNodeIndexByHash(uHash, true, iUniqueIndex);
	}
}


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::Update
// Author       : Andy Tate
// Date         : 6-Apr-2006
//
// Description  : Updates the zones themselves - loading and destroying zone-data
//				  where necessary.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::Update (const Asura_Vector_3* pxCurrentPosition,
										  const bool bBlocking)
{
	const Asura_Vector_3*			pxOldPosition	= s_pxCurrentPosition;
	Asura_Streaming_ZoneHandler*	pxOldHandler	= s_pxCallbackHandler;
	const Asura_Hash_ID				uOldZoneHash	= (m_pxCurrentZone == NULL)
													? ASURA_HASH_ID_UNSET
													: m_pxCurrentZone->GetHashID();
	bool							bOldBlocking	= s_bCallbackBlocking;

	// Preserve the current values...
	s_pxCallbackHandler				= this;
	s_pxCurrentZoneBeforeCallback	= m_pxCurrentZone;
	s_pxCurrentPosition				= pxCurrentPosition;
	s_bCallbackBlocking				= bBlocking;

	// Update each zone in-turn...
	m_bAllZonesFull		= true;
	m_bAllZonesEmpty	= true;

	m_xZoneHashTree.Traverse(UpdateZoneCallback);

	// Restore the previous values...
	s_pxCallbackHandler	= pxOldHandler;
	s_pxCurrentPosition	= pxOldPosition;
	s_bCallbackBlocking	= bOldBlocking;

	SetCurrentZone(uOldZoneHash);
}

/// End of function 'Asura_Streaming_ZoneHandler::UpdateZoneStates'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::SetCurrentZone
// Author       : Andy Tate
// Date         : 24-May-2006
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::SetCurrentZone (const Asura_Hash_ID uHash,
												  const Asura_Streaming_Zone::DataState eDataState,
												  const bool bAllowDeletion)
{
	Asura_Streaming_Zone*			pxZone		= m_xZoneHashTree.GetDataByHash(uHash);
	Asura_Streaming_ZoneDataState*	pxDataState	= (pxZone == NULL)
												? NULL
												: pxZone->GetZoneDataState(eDataState);

	// Use the handlers from this data-state...
	if(pxDataState == NULL)
	{
#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
		Asura_ResourceAllocator::SetCurrentMemoryBank(NULL);
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)

		Asura_Handler_System::SetAllZoneData(ASURA_HASH_ID_UNSET, 0, bAllowDeletion);
	}
	else
	{
		SetCurrentZoneDataState(*pxDataState, bAllowDeletion);
	}

	// Finally, set the zone...
	m_pxCurrentZone	= pxZone;
}

/// End of function 'Asura_Streaming_ZoneHandler::SetCurrentZone'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::GetUniqueIndexForZone
// Author       : Andy Tate
// Date         : 21-Apr-2006
//
// Description  : Finds the unique index for the zone with the specified hash-ID.
//
// Parameters   : The hash-ID of the zone to use.
//
// Return value : The index to be used for this zone.
////////////////////////////////////////////////////////////////////////////////

int Asura_Streaming_ZoneHandler::GetUniqueIndexForZone (const Asura_Hash_ID uHash) const
{
	// Do we have a hash-tree?
	if(m_pxUniqueIndexHashTree == NULL)
	{
		const Asura_Streaming_Zone*	pxZone	= m_xZoneHashTree.GetDataByHash(uHash);

		// No - can we find this zone?
		if(pxZone == NULL)
		{
			// No - report the error...
			Asura_Assert(false, "Cannot find a zone with this hash-ID when looking for unique indices.");
			return(0);
		}
		else
		{
			const Asura_Streaming_Zone::DataState	eDataState	= Asura_Streaming_Zone::HIGH_DETAIL_DATA;
			const Asura_Streaming_ZoneDataState*	pxDataState	= pxZone->GetZoneDataState(eDataState);

			// Do we have data to use?
			if((pxDataState != NULL) && pxDataState->IsReadyForUse())
			{
				const u_int	uDataIndex		= pxDataState->GetDataIndex();
				const u_int	uFirstDataIndex	= m_auFirstDataIndex[eDataState];

				// Yes - return the appropriate index...
				return(m_uFirstUniqueIndex + (uDataIndex - uFirstDataIndex));
			}
			else
			{
				// No - report an error...
				Asura_Assert(false, "Cannot find the high-detail data-state for zone with this hash-ID when looking for unique indices.");
				return(0);
			}
		}
	}
	else
	{
		// ART - 21/04/06 - This is nasty, but due to "Asura_HashTree" having a combined
		// query and add function we cannot use "const" where we should be able to.
		IndexHashTree*	pxIndexHashTree	= const_cast<IndexHashTree*>(m_pxUniqueIndexHashTree);

		int				iIndex = -1;

		// Do we have this zone?
		if(pxIndexHashTree->FindNodeIndexByHash(uHash, false, iIndex))
		{
			// Yes - return the index...
			return(m_uFirstUniqueIndex + iIndex);
		}
		else
		{
			// No - report the error...
			Asura_Assert(false, "Cannot find a zone with this hash-ID when looking for unique indices.");
			return(0);
		}
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::GetUniqueIndexForZone'. ///


#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)

////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::AllocateMemoryBanks
// Author       : Andy Tate
// Date         : 19-Apr-2006
//
// Description  : Goes through ALL of the memory-banks and attempts to 
//				  re-initialise each one in-turn.
//
// Parameters   : A pointer to the format text to use when creating memory-banks
//				  and a pointer to the memory-bank allocation function.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::AllocateMemoryBanks (const char** pszFormatText,
													   MemoryBankAllocationFunction* pfnCreateMemoryBank)
{
	const u_int	uSizeOfTempText	= 64;
	Asura_Char	szTempText[uSizeOfTempText];

	// Remove the existing memory-banks...
	DeallocateMemoryBanks();

	// Create the necessary memory-banks...
	for(u_int u = 0; u < Asura_Streaming_Zone::MAX_DATA_STATES; u++)
	{
		const u_int	uNumMemoryBanks	= m_auMaxDataIndices[u];

		// Allocate the array of pointers...
		m_appxZoneMemoryBanks[u]	= new Asura_MemoryBank* [uNumMemoryBanks];

		// Initialise the individual memory-banks...
		for(u_int v = 0; v < uNumMemoryBanks; v++)
		{
			Asura_Streaming_Zone::DataState	eDataState	= static_cast<Asura_Streaming_Zone::DataState>(u);

			Asura_MemoryBank*				pxMemoryBank;

			// Create this memory-bank...
			Asura_PrintfToBfr(szTempText, pszFormatText[u], uSizeOfTempText, true, v);
			pxMemoryBank	= pfnCreateMemoryBank(eDataState, v, szTempText);

#if defined(ASURA_MEMORYBANK_ALLOW_STATS)
			// Initialise the memory-bank stats...
			if(pxMemoryBank != NULL)
			{
				pxMemoryBank->CreateStats(ASURA_MAX_RESOURCE_TYPES, Asura_ResourceAllocator::GetResourceNameArray());
			}
#endif	// defined(ASURA_MEMORYBANK_ALLOW_STATS)

			// Store this memory-bank...
			m_appxZoneMemoryBanks[u][v]	= pxMemoryBank;
		}
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::AllocateMemoryBanks'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::DeallocateMemoryBanks
// Author       : Andy Tate
// Date         : 19-Apr-2006
//
// Description  : Deallocates any memory-banks which have previously been 
//				  allocated.
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

void Asura_Streaming_ZoneHandler::DeallocateMemoryBanks (void)
{
	// Create the necessary memory-banks...
	for(u_int u = 0; u < Asura_Streaming_Zone::MAX_DATA_STATES; u++)
	{
		// Do we have any memory-banks?
		if(m_appxZoneMemoryBanks[u] != NULL)
		{
			const u_int	uNumMemoryBanks	= m_auMaxDataIndices[u];

			// Destroy each memory-bank in-turn...
			for(u_int v = 0; v < uNumMemoryBanks; v++)
			{
				delete(m_appxZoneMemoryBanks[u][v]);
			}

			// Deallocate the array of pointers...
			delete[](m_appxZoneMemoryBanks[u]);
			m_appxZoneMemoryBanks[u]	= NULL;
		}
	}
}

/// End of function 'Asura_Streaming_ZoneHandler::DeallocateMemoryBanks'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::GetMemoryBankFromDataIndex
// Author       : Andy Tate
// Date         : 19-Apr-2006
//
// Description  : Returns the memory-bank appropriate to the specified data-index.
//
// Parameters   : The data-index to use for the memory-bank.
//
// Return value : A pointer to the memory-bank found, NULL if index out-of-range.
////////////////////////////////////////////////////////////////////////////////

Asura_MemoryBank* Asura_Streaming_ZoneHandler::GetMemoryBankFromDataIndex (const u_int uDataIndex) const
{
	// Which memory-bank are we checking?
	for(u_int u = 0; u < Asura_Streaming_Zone::MAX_DATA_STATES; u++)
	{
		const u_int	uFirstDataIndex	= m_auFirstDataIndex[u];
		const u_int	uLastDataIndex	= (uFirstDataIndex + m_auMaxDataIndices[u]) - 1;

		// Is our data-index within this range?
		if((uDataIndex >= uFirstDataIndex) && (uDataIndex <= uLastDataIndex))
		{
			const u_int	uZoneIndex	= uDataIndex - uFirstDataIndex;

			// Return the memory-bank...
			return(m_appxZoneMemoryBanks[u][uZoneIndex]);
		}
	}

	// Data-index not found...
	return(NULL);
}

/// End of function 'Asura_Streaming_ZoneHandler::GetMemoryBankFromDataIndex'. ///


#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)


////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::GetResetGroupFromDataIndex
// Author       : Andy Tate
// Date         : 27-Mar-2006
//
// Description  : Finds the texture reset-group corresponding to the specified
//				  data-index.
//
// Parameters   : The data-index to be used.
//
// Return value : The reset-group corresponding to the data-index.
////////////////////////////////////////////////////////////////////////////////

u_int Asura_Streaming_ZoneHandler::GetResetGroupFromDataIndex (const u_int uDataIndex)
{
#if defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
	// Which memory-bank are we checking?
	for(u_int u = 0; u < Asura_Streaming_Zone::MAX_DATA_STATES; u++)
	{
		const u_int	uFirstDataIndex	= m_auFirstDataIndex[u];
		const u_int	uLastDataIndex	= (uFirstDataIndex + m_auMaxDataIndices[u]) - 1;

		// Is our data-index within this range?
		if((uDataIndex >= uFirstDataIndex) && (uDataIndex <= uLastDataIndex))
		{
			return(ASURA_TEXTURE_RESETTABLE_STREAMING_GROUP01 + uDataIndex);
		}
	}

	// Data-index not found...
	return(ASURA_TEXTURE_NOT_LOADED);
#else
	// Just use the first reset-group...
	return(ASURA_TEXTURE_RESETTABLE_STREAMING_GROUP01);
#endif	// defined(ASURA_RESOURCE_ALLOCATOR_USES_MEMORY_BANK)
}

/// End of function 'Asura_Streaming_ZoneHandler::GetResetGroupFromDataIndex'. ///



////////////////////////////////////////////////////////////////////////////////
// Function     : Asura_Streaming_ZoneHandler::CalculateOffsets
// Author       : Richard Rice
// Date         : 6-Mar-2006
//
// Description  : Calculates offsets to and from zone
//
// Parameters   : Zone to do offsets for
//
// Return value : None.
////////////////////////////////////////////////////////////////////////////////

static Asura_Streaming_Zone* g_pxCalculateOffsetsZone;

static void CalculateOffsetsCallback(Asura_Hash_ID uHash, Asura_Streaming_Zone* pxZone)
{
	for(u_int i=0;i<pxZone->GetNumConnection();i++)
	{
		Asura_Streaming_Connection* pxConnection = pxZone->GetConnection(i);
		if(pxConnection->GetToZoneHashID() == g_pxCalculateOffsetsZone->GetHashID())
		{
			pxConnection->SetRelativeOffset(g_pxCalculateOffsetsZone->GetPosition() - pxZone->GetPosition());
			break;
		}
	}	
}

void Asura_Streaming_ZoneHandler::CalculateOffsets (Asura_Streaming_Zone* pxZone)
{
	Asura_Assert(pxZone,"");

	//deal with connections from this zone to other zones
	for(u_int i=0;i<pxZone->GetNumConnection();i++)
	{
		Asura_Streaming_Connection* pxConnection = pxZone->GetConnection(i);

		Asura_Streaming_Zone* pxOtherZone = FindZone(pxConnection->GetToZoneHashID());
		if(pxOtherZone)
		{
			pxConnection->SetRelativeOffset(pxOtherZone->GetPosition() - pxZone->GetPosition());
		}
	}
	//and other zones to this zone
	g_pxCalculateOffsetsZone = pxZone;
	m_xZoneHashTree.Traverse(CalculateOffsetsCallback);
}

/// End of function 'Asura_Streaming_ZoneHandler::CalculateOffsets'. ///

#endif	// ASURA_USE_STREAMING

/// End of file. ///
