예제 #1
0
void USwFMOD::Update( FPointRegion Region, FCoords& Coords )
{
	guard(USwFMOD::Update);
	FMOD_RESULT result;
	//SWF_LOG( NAME_DevSound, TEXT("%s >> %s :: [%s],[%s]"), SWF_PLOG, *ToStr(Region), *ToStr(Coords) );

	if( !Viewport || !Viewport->IsValid() || !Viewport->Actor || !Viewport->Actor->IsValid() )
		return;

	// Load geometry
	if( bOcclusion )
	{
		static ULevel* level = NULL;
		if( Viewport->Actor->GetLevel() != level )
		{
			level = Viewport->Actor->GetLevel();
			LoadGeometry();
		}
	}

	// Time passes...
	DOUBLE DeltaTime = appSeconds() - LastTime;
	LastTime += DeltaTime;
	DeltaTime = Clamp( DeltaTime, 0.0, 1.0 );

	// Get viewactor
	AActor* ViewActor = Viewport->Actor->ViewTarget ? Viewport->Actor->ViewTarget : Viewport->Actor;
	
	// Is viewport realtime ?
	UBOOL Realtime = Viewport->IsRealtime() && Viewport->Actor->Level->Pauser == TEXT("");
	
	// Default viewport coords
	FVector location = FVector(0,0,0);
	FVector velocity = FVector(0,0,0);
	FVector forward = FVector(1,0,0);
	FVector up = FVector(0,0,1);


	//
	// Update listener
	//
	guard(USwFMODAudio::Update::UpdateListener);

	// Use ViewActor coords
	FCoords coords = GMath.UnitCoords / ViewActor->Rotation;;
	coords.Origin = ViewActor->Location;

	// Set viewport coords
	location = coords.Origin;
	velocity = ViewActor->Velocity;
	forward = coords.XAxis;
	up = coords.ZAxis;

	// verify
	SWF_VERIFY_FVECTOR(location);
	SWF_VERIFY_FVECTOR(velocity);
	SWF_VERIFY_FVECTOR_NORMAL(forward);
	SWF_VERIFY_FVECTOR_NORMAL(up);

	// Default listener coords
	FMOD_VECTOR fm_pos = {0,0,0};
	FMOD_VECTOR fm_vel = {0,0,0};
	FMOD_VECTOR fm_fwd = {0,0,1};
	FMOD_VECTOR fm_up  = {0,1,0};

	// Set listener coords
	fm_pos = ToFMODVector(location);
	fm_vel = ToFMODVector(velocity);
	fm_fwd = ToFMODNormal(forward);
	fm_up = ToFMODNormal(up);

	// verify
	SWF_VERIFY_FMODVECTOR(fm_pos);
	SWF_VERIFY_FMODVECTOR(fm_vel);
	SWF_VERIFY_FMODVECTOR_NORMAL(fm_fwd);
	SWF_VERIFY_FMODVECTOR_NORMAL(fm_up);

	// Update
	SWF_FMOD_CALL( System->set3DListenerAttributes( 0, &fm_pos, &fm_vel, &fm_fwd, &fm_up ) );
	unguard;


	//
	// Zone effects
	//
	guard(USwFMODAudio::Update::UpdateZone);

	/*// Default zone properties
	UBOOL bWaterZone = 0;
	UBOOL bReverbZone = 0;
	UBOOL bRaytraceReverb = 0;
	BYTE MasterGain = 100;
	INT CutoffHz = 6000;
	BYTE Delay[6] = {20,34,0,0,0,0};
	BYTE Gain[6] = {150,70,0,0,0,0};

	// Get zone properties
	if( Region.Zone && Region.Zone->IsValid() )
	{
		bWaterZone = Region.Zone->bWaterZone;
		bReverbZone = Region.Zone->bReverbZone;
		bRaytraceReverb = Region.Zone->bRaytraceReverb;
		MasterGain = Region.Zone->MasterGain;
		CutoffHz = Region.Zone->CutoffHz;
		appMemcpy(Delay, Region.Zone->Delay, 6*sizeof(BYTE));
		appMemcpy(Gain, Region.Zone->Gain, 6*sizeof(BYTE));
	}*/
	unguard;



	//
	// Update sounds.
	//
	guard(USwFMODAudio::Update::UpdateSounds);

	// Iterate through all channels
	for( FSwChannelEnumerator it(System); it; ++it )
	{
		FMOD::Channel* channel = *it;
		if( !IsChannelValid(channel) )
			continue;

		// Channel data
		FMOD::Sound* sample = GetChannelSample(channel);
		if( !sample )
			continue;

		UObject* object = GetSampleObject(sample);
		if( !object )
			continue;
		USound* sound = Cast<USound>(object);

		FSwSoundId id = GetChannelId(channel);
		AActor* actor = id.GetActor();

		FMOD_MODE fmode;
		SWF_FMOD_CALL( channel->getMode(&fmode) );
		UBOOL bIs3D = HasFlag(fmode,FMOD_3D);

		// Sample defaults
		FLOAT deffrequency;
		FLOAT defvolume;
		FLOAT defpanning;
		INT defpriority;
		SWF_FMOD_CALL( sample->getDefaults( &deffrequency, &defvolume, &defpanning, &defpriority ) );
	

		if( sound )
		{
			if( actor && actor->IsValid() )
			{
				// Ambient sounds
				if( id.GetSlot() == SLOT_Ambient )
				{
					if(	actor->AmbientSound != sound
					||	FDist(location, actor->Location) > actor->WorldSoundRadius() + AmbientHysteresis
					||  !Realtime )
					{
						// Stop ambient sound
						//SWF_LOG( NAME_DevSound, TEXT("%s -- %s :: Ambient OUT [%d]"), SWF_PLOG, *ToStr(id) );
						SWF_FMOD_CALL( channel->setUserData(NULL) );
						SWF_FMOD_CALL( channel->stop() );
						continue;
					}
					else
					{
						// Update ambient sound properties.
						FLOAT volume = actor->SoundVolume / 255.0f;
						FLOAT frequency = (actor->SoundPitch / 64.0f) * deffrequency;

						SWF_VERIFY_FLOAT(volume);
						SWF_VERIFY_FLOAT(frequency);

						SWF_FMOD_CALL( channel->setVolume( volume ) );
						SWF_FMOD_CALL( channel->setFrequency( frequency ) );

						if( bIs3D )
						{
							// Update 3D sound properties
							FLOAT mindist = ToFMODFloat(DistanceMin);
							FLOAT radius = ToFMODFloat( actor->WorldSoundRadius() );
							TSwSortMinMax( mindist, radius );

							SWF_VERIFY_FLOAT(radius);
							SWF_VERIFY_FLOAT(mindist);

							SWF_FMOD_CALL( channel->set3DMinMaxDistance( mindist, radius ) );
						}
					}
				}

				if( bIs3D )
				{
					// Update 3D sound properties
					FMOD_VECTOR snd_pos = ToFMODVector(actor->Location);
					FMOD_VECTOR snd_vel = ToFMODVector(actor->Velocity);
			
					SWF_VERIFY_FMODVECTOR(snd_pos);							
					SWF_VERIFY_FMODVECTOR(snd_vel);

					SWF_FMOD_CALL( channel->set3DAttributes(&snd_pos, &snd_vel) );
				}
			}
		}
	}
	unguard;


	//
	// Play ambient sounds
	//
	if( Realtime )
	{
		guard(USwFMODAudio::Update::PlayAmbient);
		for( INT i=0; i<Viewport->Actor->GetLevel()->Actors.Num(); ++i )
		{
			AActor* Actor = Viewport->Actor->GetLevel()->Actors(i);
			if(	Actor
			&&	Actor->AmbientSound
			&&	FDist(location, Actor->Location) <= Actor->WorldSoundRadius() )
			{
				FSwSoundId ambientid = FSwSoundId(Actor,SLOT_Ambient,0);
				//SWF_LOG( NAME_DevSound, TEXT("%s -- %s :: Ambient TEST IN [%s]"), SWF_PLOG, *ToStr(ambientid) );

				// Find this sound in currently playing ones
				FMOD::Channel* ambientchannel = NULL;
				for( FSwChannelEnumerator it(System,AmbientChannels); it; ++it )
				{
					FMOD::Channel* channel = *it;
					if( IsChannelValid(channel) && GetChannelId(channel) == ambientid )
					{
						//SWF_LOG( NAME_DevSound, TEXT("%s -- %s :: Ambient FOUND IN [%s]"), SWF_PLOG, *ToStr(GetChannelId(channel)) );
						ambientchannel = channel;
						break;
					}
				}

				// If not found play ambient
				if( !ambientchannel )
				{
					//SWF_LOG( NAME_DevSound, TEXT("%s -- %s :: Ambient PLAY IN [%s][%s]"), SWF_PLOG, *ToStr(ambientid), *ToStr(Actor->AmbientSound) );
					PlaySound( Actor, ambientid.GetId(), Actor->AmbientSound, Actor->Location, Actor->SoundVolume/255.0f, Actor->WorldSoundRadius(), Actor->SoundPitch/64.0f );
				}
			}
		}
		unguard;
	}
	

	//
	// Music
	//
	guard(UpdateMusic)

/*	REQUIREMENTS

	SongSection is updated at realtime by audio sys
	MTRAN_Fade* only fade out, not in
	music changes caused by transition only
	ttransition reset on change
	MTRAN_None = don't change
	MTRAN_Instant = instant change
	MTRAN_Segue = seamless?
	MTRAN_Fade = 1s fade
	MTRAN_FastFade = 1/3s fade
	MTRAN_SlowFade = 5s fade
*/
	// find music channel
	FMOD::Channel* musicchannel = NULL;
	for( FSwChannelEnumerator it(System,MusicChannels); it; ++it )
	{
		FMOD::Channel* channel = *it;
		if( !IsChannelValid(channel) )
			continue;

		if( !musicchannel )
		{
			musicchannel = channel;
		}
		else
		{
			// there can be only one music
			SWF_LOG( NAME_DevSound, TEXT("%s :: %s :: StopMusic %s"), SWF_PLOG, *PrintChannel(channel) );
			SWF_FMOD_CALL( channel->setUserData(NULL) );
			SWF_FMOD_CALL( channel->stop() );
		}
	}

	if( Viewport->Actor->Transition != MTRAN_None )
	{
		// init fading
		if( MusicFade < 0 )
		{
			SWF_LOG( NAME_DevSound, TEXT("%s :: %s :: Music transition %s S:%s T:%s "), SWF_PLOG
				, *ToStr(Viewport->Actor->Song)
				, *ToStr(Viewport->Actor->SongSection)
				, *ToStr(Viewport->Actor->Transition));

			switch( Viewport->Actor->Transition )
			{
				case MTRAN_Instant:		MusicFadeTime = -1.0f;	break;	// Instant
				case MTRAN_Segue:		MusicFadeTime = -1.0f;			// Instant precached
					if( Viewport->Actor->Song && !Viewport->Actor->Song->Handle )
						RegisterMusic(Viewport->Actor->Song);
					break;
				case MTRAN_Fade:		MusicFadeTime = 1.0f;	break;	// 1s fadeout
				case MTRAN_FastFade:	MusicFadeTime = 0.33f;	break;	// 1/3s fadeout
				case MTRAN_SlowFade:	MusicFadeTime = 5.0f;	break;	// 5s fadeout
				default:				MusicFadeTime = -1.0f;	break;	// Unknown,instant
					
			}
			MusicFade = MusicFadeTime;
		}

		// deduct delta
		MusicFade -= DeltaTime;
		//SWF_LOG( NAME_DevSound, TEXT("%s << %s :: MusicFade %s %s"), SWF_PLOG, *ToStr(MusicFade), *ToStr(MusicFadeTime) );

		if( MusicFade > 0 )
		{
			// fade volume
			if( musicchannel && MusicFadeTime > 0 )
			{
				SWF_FMOD_CALL( musicchannel->setVolume( MusicFade / MusicFadeTime) );
			}
		}
		else
		{
			// play new
			MusicFade = -1;
			Viewport->Actor->Transition = MTRAN_None;
			PlayMusic( Viewport->Actor->Song, musicchannel, Viewport->Actor->SongSection, Viewport->Actor->CdTrack, static_cast<EMusicTransition>(Viewport->Actor->Transition) );
		}
	}
	else
	{
		// Update section
		if( musicchannel )
		{
			// update section
			// FIXME:: getPosition doesn't work with volume 0 (virtual?)
			UINT sec = 0;
			result = musicchannel->getPosition(&sec,FMOD_TIMEUNIT_MODORDER);
			if( result == FMOD_OK )
			{
				Viewport->Actor->SongSection = sec;
			}

			// Update position
			if( IsChannelValid(musicchannel) )
			{
				UINT row = 0;
				result = musicchannel->getPosition(&row,FMOD_TIMEUNIT_MODROW);
				if( result == FMOD_OK )
				{
					// IT/MOD/XM
					UINT pattern = 0;
					result = musicchannel->getPosition(&pattern,FMOD_TIMEUNIT_MODPATTERN);
					if( result == FMOD_OK )
					{
						MusicPositions(Viewport->Actor->SongSection).row = row;
						MusicPositions(Viewport->Actor->SongSection).pattern = pattern;
					}
				}
				else
				{
					// MPEG/OGG
					UINT ms = 0;
					result = musicchannel->getPosition(&ms,FMOD_TIMEUNIT_MS);
					if( result == FMOD_OK )
					{
						MusicPositions(Viewport->Actor->SongSection).ms = ms;
					}
				}
			}
		}
		else if( Viewport->Actor->Song && VolumeMusic > 0 )
		{
			// Restart missing/dropped song (bad channel priorities?)
			SWF_LOG( NAME_DevSound, TEXT("%s :: %s :: Restarting missing song %s S:%s T:%s "), SWF_PLOG
				, *ToStr(Viewport->Actor->Song)
				, *ToStr(Viewport->Actor->SongSection)
				, *ToStr(Viewport->Actor->Transition));
			Viewport->Actor->Transition = MTRAN_Instant;
		}
	}



	unguard;

	// Update FMOD
	guard(UpdateFMOD);
	SWF_FMOD_CALL( System->update() );
	unguard;

	//SWF_LOG( NAME_DevSound, TEXT("%s << %s :: [%s],[%s]"), SWF_PLOG, *ToStr(Region), *ToStr(Coords) );
	unguard;
}
예제 #2
0
void UXViewport::Tick()
{
	guard(UXViewport::Tick);
	UXClient* Client = GetOuterUXClient();

	if (!XWindow)
		return;

	// Keyboard.
	EInputKey Key;
	KeySym LowerCase, UpperCase;

	// Mouse movement management.
	UBOOL MouseMoved;
	INT BaseX = SizeX/2;
	INT BaseY = SizeY/2;
	INT DX = 0, DY = 0;

	XEvent Event;
	while( XPending(XDisplay) )
	{
		XNextEvent(XDisplay, &Event);
		switch( Event.type )
		{
			case CreateNotify:
				// Window has been created.
				ViewportStatus = X_ViewportNormal;

				// Make this viewport current and update its title bar.
				GetOuterUClient()->MakeCurrent( this );				
				break;
			case DestroyNotify:
				// Window has been destroyed.
				if( BlitFlags & BLIT_Fullscreen )
					EndFullscreen();

				if( ViewportStatus == X_ViewportNormal )
				{
					// Closed normally.
					ViewportStatus = X_ViewportClosing;
					delete this;
				}
				break;
			case Expose:
				// Redraw the window.
				break;
			case KeyPress:
				// Reset timer.
				RepeatTimer = appSeconds();
				LastKey = True;

				// Get key code.
				Key = (EInputKey) XKeycodeToKeysym( XDisplay, Event.xkey.keycode, 0 );
				XConvertCase( Key, &LowerCase, &UpperCase );
				Key = (EInputKey) UpperCase;

				// Check the Keysym map.
				if (KeysymMap[Key] != 0)
					Key = (EInputKey) KeysymMap[Key];

				// Send key to input system.
				CauseInputEvent( Key, IST_Press );

				// Emulate WM_CHAR.
				// Check for shift modifier.
				if (Event.xkey.state & ShiftMask)
				{
					Key = (EInputKey) UpperCase;
					if (ShiftMaskMap[Key] != 0)
						Key = (EInputKey) ShiftMaskMap[Key];
				} else
					Key = (EInputKey) LowerCase;

				if (Key == XK_BackSpace)
					Key = IK_Backspace;
				if (Key == XK_Tab)
					Key = IK_Tab;
				if (Key == XK_Return)
					Key = IK_Enter;

				if (WMCharMap[Key] == 1)
				{
					KeyRepeatMap[Key] = 1;

					Client->Engine->Key( this, Key );
				}
				break;
			case KeyRelease:
				// Get key code.
				Key = (EInputKey) XKeycodeToKeysym( XDisplay, Event.xkey.keycode, 0 );
				XConvertCase( Key, &LowerCase, &UpperCase );
				Key = (EInputKey) UpperCase;

				// Check the Keysym map.
				if (KeysymMap[Key] != 0)
					Key = (EInputKey) KeysymMap[Key];

				// Send key to input system.
				CauseInputEvent( Key, IST_Release );

				// Release all types of this key.
				if (Key == XK_BackSpace)
					Key = IK_Backspace;
				if (Key == XK_Tab)
					Key = IK_Tab;
				if (Key == XK_Return)
					Key = IK_Enter;

				KeyRepeatMap[Key] = 0;
				KeyRepeatMap[ShiftMaskMap[Key]] = 0;
				KeyRepeatMap[ShiftMaskMap[(EInputKey) LowerCase]] = 0;
				KeyRepeatMap[(EInputKey) LowerCase] = 0;
				break;
			case ButtonPress:
				switch (Event.xbutton.button)
				{
					case 1:
						Key = IK_LeftMouse;
						break;
					case 2:
						Key = IK_MiddleMouse;
						break;
					case 3:
						Key = IK_RightMouse;
						break;
					case 4:
						Key = IK_MouseWheelUp;
						break;
					case 5:
						Key = IK_MouseWheelDown;
						break;
				}

				// Send to input system.
				CauseInputEvent( Key, IST_Press );
				break;
			case ButtonRelease:
				switch (Event.xbutton.button)
				{
					case 1:
						Key = IK_LeftMouse;
						break;
					case 2:
						Key = IK_MiddleMouse;
						break;
					case 3:
						Key = IK_RightMouse;
						break;
					case 4:
						Key = IK_MouseWheelUp;
						break;
					case 5:
						Key = IK_MouseWheelDown;
						break;
				}

				// Send to input system.
				CauseInputEvent( Key, IST_Release );
				break;
			case MotionNotify:
				MouseMoved = True;

				if (UseDGA)
				{
					if (abs(Event.xmotion.x_root) > 1)
						DX += Event.xmotion.x_root * 2;
					else
						DX += Event.xmotion.x_root;
					if (abs(Event.xmotion.y_root) > 1)
						DY += Event.xmotion.y_root * 2;
					else
						DY += Event.xmotion.y_root;
				} else {
					DX += Event.xmotion.x - BaseX;
					DY += Event.xmotion.y - BaseY;
					BaseX = Event.xmotion.x;
					BaseY = Event.xmotion.y;
				}
				break;
			case ResizeRequest:
				// Eventually resize and setres.
				break;
			case MapNotify:
				if (Iconified)
				{
					guard(Uniconify);
					Iconified = false;

					// Unpause the game if applicable.
					Exec( TEXT("SETPAUSE 0"), *this );

					// Reset the input buffer.
					Input->ResetInput();

					// Snag the mouse again.
					SetMouseCapture( 1, 1, 0 );

					// Make this viewport current.
					GetOuterUClient()->MakeCurrent( this );

					// Turn off that damn auto repeat.
					XAutoRepeatOff( XDisplay );

					// Return to fullscreen.
					if( BlitFlags & BLIT_Fullscreen )
						TryRenderDevice( TEXT("ini:Engine.Engine.GameRenderDevice"), INDEX_NONE, INDEX_NONE, ColorBytes, 1 );
					unguard;
				}
				break;
			case UnmapNotify:
				if (!Iconified)
					Iconify();
				break;
			case FocusIn:
				break;
			case FocusOut:
				Iconify();
				break;
		}
	}

	if (Iconified)
		return;

	// Deliver mouse behavior to the engine.
	if (MouseMoved)
	{
		if (!UseDGA)
		{
			XWarpPointer(XDisplay, None, XWindow,
				0, 0, 0, 0, SizeX/2, SizeY/2);

			// Clear out the warp.
			XEvent MouseEvent;
			while( XCheckWindowEvent(XDisplay, XWindow, ButtonMotionMask | PointerMotionMask, &MouseEvent) )
			{
				// Do Nothing.
			}
		}
		
		// Send to input subsystem.
		if( DX )
			CauseInputEvent( IK_MouseX, IST_Axis, +DX );
		if( DY )
			CauseInputEvent( IK_MouseY, IST_Axis, -DY );
	}
	
	// Send WM_CHAR for down keys.
	if ( LastKey && (appSeconds() - RepeatTimer < 0.5) )
		return;
	LastKey = False;
	if ( appSeconds() - RepeatTimer < 0.1 )
		return;

	RepeatTimer = appSeconds();
	for (INT i=0; i<256; i++)
		if (KeyRepeatMap[i] != 0)
		{
			if (i == IK_Backspace)
			{
				CauseInputEvent( i, IST_Press );
				CauseInputEvent( i, IST_Release );				
			}
			else
				Client->Engine->Key( this, (EInputKey) i );
		}
	
	unguard;
}
예제 #3
0
/**
 * Defragment the memory. Memory is moved around using the specified policy.
 * The function tries to perform non-overlapping memory transfers as much as possible.
 */
void FBestFitAllocator::DefragmentMemory( FDefragmentationPolicy &Policy )
{
#if !FINAL_RELEASE || FINAL_RELEASE_DEBUGCONSOLE
	DOUBLE StartTime		= appSeconds();
	INT NumHolesBefore		= 0;
	INT NumHolesAfter		= 0;
	INT LargestHoleBefore	= GetLargestAvailableAllocation(&NumHolesBefore);
	INT LargestHoleAfter	= 0;
#endif

	INT TotalRelocationSize	= 0;
	INT NumRelocations		= 0;

	// Find the first free chunk.
	FMemoryChunk* AvailableChunk = FirstChunk;
	while ( AvailableChunk && !AvailableChunk->bIsAvailable )
	{
		AvailableChunk = AvailableChunk->NextChunk;
	}
	// Process the next used chunk.
	FMemoryChunk* Chunk = AvailableChunk ? AvailableChunk->NextChunk : NULL;

	// Relocate all subsequent used chunks to the beginning of the free chunk.
	while ( Chunk )
	{
		FMemoryChunk* BestChunk = AvailableChunk;

#if MINIMIZE_NUMBER_OF_OVERLAPPING_RELOCATIONS
		// Would this be an overlapped memory-move?
		if ( Chunk->Size > AvailableChunk->Size )
		{
			// Try to move it out of the way (to the last possible free chunk)!
			FMemoryChunk* AnotherAvailableChunk = FirstFreeChunk;
			while ( AnotherAvailableChunk )
			{
				if ( AnotherAvailableChunk->Size >= Chunk->Size && AnotherAvailableChunk->Base > BestChunk->Base )
				{
					BestChunk = AnotherAvailableChunk;
				}
				AnotherAvailableChunk = AnotherAvailableChunk->NextFreeChunk;
			}
		}
#endif

		UBOOL bCouldRelocate = Policy.Relocate(BestChunk->Base, Chunk->Base, Chunk->Size);
		if ( bCouldRelocate )
		{
			NumRelocations++;
			TotalRelocationSize += Chunk->Size;

			// Update our book-keeping.
			PointerToChunkMap.Remove((PTRINT) Chunk->Base);
			PointerToChunkMap.Set((PTRINT) BestChunk->Base, BestChunk);
			BestChunk->UnlinkFree();			// Mark as being in use.
			if ( BestChunk->Size > Chunk->Size )
			{
				Split(BestChunk, Chunk->Size);	// Split to create a new free chunk
			}
			else if ( BestChunk->Size < Chunk->Size )
			{
				// Overlapping relocation. We're just "sliding" memory down one step.
				check( Chunk->PreviousChunk == BestChunk );
				INT HoleSize	= BestChunk->Size;
				BestChunk->Size	= Chunk->Size;
				Chunk->Base		= BestChunk->Base + BestChunk->Size;
				Chunk->Size		= HoleSize;
			}

			// Free this chunk and Coalesce.
			FreeChunk(Chunk);
		}
		else
		{
			// Got a non-relocatable used chunk. Try relocate as many others into the free chunk as we can and move on.
			FMemoryChunk* AnotherUsedChunk = Chunk->NextChunk;
			while ( AnotherUsedChunk && AnotherUsedChunk->bIsAvailable )
			{
				AnotherUsedChunk = AnotherUsedChunk->NextChunk;
			}
			while ( AnotherUsedChunk && AvailableChunk && AvailableChunk->bIsAvailable )
			{
				// Find the next used chunk now, before we free the current one.
				FMemoryChunk* NextUsedChunk = AnotherUsedChunk->NextChunk;
				while ( NextUsedChunk && NextUsedChunk->bIsAvailable )
				{
					NextUsedChunk = NextUsedChunk->NextChunk;
				}
				if ( AnotherUsedChunk->Size <= AvailableChunk->Size )
				{
					if ( Policy.Relocate(AvailableChunk->Base, AnotherUsedChunk->Base, AnotherUsedChunk->Size) )
					{
						NumRelocations++;
						TotalRelocationSize += AnotherUsedChunk->Size;

						// Update our book-keeping.
						PointerToChunkMap.Remove((PTRINT) AnotherUsedChunk->Base);
						PointerToChunkMap.Set((PTRINT) AvailableChunk->Base, AvailableChunk);
						AvailableChunk->UnlinkFree();						// Mark as being in use.
						if ( AvailableChunk->Size > AnotherUsedChunk->Size )
						{
							Split(AvailableChunk, AnotherUsedChunk->Size);	// Split to create a new free chunk
							AvailableChunk = AvailableChunk->NextChunk;
						}
						else
						{
							check( AvailableChunk->Size == AnotherUsedChunk->Size );
						}
						FreeChunk(AnotherUsedChunk);
					}
				}
				AnotherUsedChunk = NextUsedChunk;
			}
			// AvailableChunk is now filled up as much as possible. Skip it.
			AvailableChunk = AvailableChunk ? AvailableChunk->NextChunk : NULL;
		}

		// If we used up our current free chunk, find the next one.
		while ( AvailableChunk && !AvailableChunk->bIsAvailable )
		{
			AvailableChunk = AvailableChunk->NextChunk;
		}

		// Process the next used chunk.
		Chunk = AvailableChunk ? AvailableChunk->NextChunk : NULL;
	}

#if !FINAL_RELEASE
	DOUBLE Duration = appSeconds() - StartTime;
	LargestHoleAfter = GetLargestAvailableAllocation( &NumHolesAfter );
	debugf( TEXT("DEFRAG: %.1f ms, Available: %.3f MB, NumRelocations: %d, Relocated: %.3f MB, NumHolesBefore: %d, NumHolesAfter: %d, LargestHoleBefore: %.3f MB, LargestHoleAfter: %.3f MB"),
		Duration*1000.0, AvailableMemorySize/1024.0f/1024.0f, NumRelocations, FLOAT(TotalRelocationSize)/1024.0f/1024.0f, NumHolesBefore, NumHolesAfter, FLOAT(LargestHoleBefore)/1024.f/1024.0f, FLOAT(LargestHoleAfter)/1024.0f/1024.0f );
#endif
}