//
// audioengine/entity.cpp
//
// Copyright (C) 1999-2006 Rockstar Games.  All Rights Reserved.
//

#include "entity.h"

#include "controller.h"
#include "widgets.h"
#include "engine.h"

#include "audiohardware/channel.h"
#include "audiohardware/driver.h"
#include "audiohardware/debug.h"
#include "profile/group.h"
#include "profile/page.h"
#include "system/param.h"

namespace rage {

CompileTimeAssert(AUD_INVALID_CONTROLLER_ENTITY_ID == 0xFFFF);
#define AUDCONTROLLER_USER_PROFILER 0
#if AUDCONTROLLER_USER_PROFILER
PF_PAGE(AudioEntitiesTimingsPage, "RAGE audEntity Timings");
PF_GROUP(AudioEntitiesTimings);
PF_LINK(AudioEntitiesTimingsPage, AudioEntitiesTimings);
PF_TIMER(SetInitialSoundParams, AudioEntitiesTimings);
#endif

BANK_ONLY(bool g_WarnOnMissingSounds = false);

audMetadataRef g_NullSoundRef;

atRangeArray<audVariableBlock, g_MaxAudEntityVariableBlocks> audEntity::sm_EntityVariableBlocks;
ALIGNAS(16) atRangeArray<float, g_MaxAudEntityVariableBlocks*audVariableBlock::kNumVariablesPerBlock> audEntity::sm_EntityVariableBlockValues ;
u32 * audEntity::sm_EntityVariableBlockRefs = NULL;

audEntity::audBatchedSoundRequestList audEntity::sm_BatchedSoundRequests;
audEntity::audDeletedEntityList audEntity::sm_DeletedEntities;
audEntity::audDeletedTrackerList audEntity::sm_DeletedTrackers;

BANK_ONLY(bool audEntity::sm_PrintedFullBatchList = false);

void audEntity::InitClass()
{
	sm_BatchedSoundRequests.Reset();
	sm_DeletedEntities.Reset();
	sm_EntityVariableBlockRefs = rage_new u32[g_MaxAudEntityVariableBlocks];
	InitializeVariableBlocks();
}

void audEntity::ShutdownClass()
{
	if(sm_EntityVariableBlockRefs)
	{
		delete[] sm_EntityVariableBlockRefs;
		sm_EntityVariableBlockRefs = NULL;
	}
}
#define AUD_ENABLE_BATCHING 1
audEntity::audEntity() : m_ControllerEntityId(AUD_INVALID_CONTROLLER_ENTITY_ID)
{
	m_EntityVariableBlock = -1;
#if __DEV
	m_Name = NULL;
	m_IsADebugEntity = false;
#endif
}

audEntity::~audEntity()
{
	Shutdown();
}

void audEntity::Init()
{
	if (!audDriver::GetConfig().IsAudioProcessingEnabled())
	{
		return;
	}

	Assert(m_ControllerEntityId == AUD_INVALID_CONTROLLER_ENTITY_ID);

	if (AssertVerify(audController::GetController()))
	{
		Assign(m_ControllerEntityId, audController::GetController()->RegisterEntity(this));

#if __BANK
		if(g_AudioEngine.GetRemoteDebug().IsDebugTypeActive(ENTITY_DEBUG))
		{
			g_AudioEngine.GetRemoteDebug().AddEntity(this);
		}
#endif
	}
}

void audEntity::InitializeVariableBlocks()
{
	sysMemZeroBytes<sizeof(sm_EntityVariableBlocks)>(&sm_EntityVariableBlocks);
	sysMemZeroBytes<sizeof(sm_EntityVariableBlockValues)>(&sm_EntityVariableBlockValues);
	sysMemZeroBytes<sizeof(u32)*g_MaxAudEntityVariableBlocks>(sm_EntityVariableBlockRefs);
}

void audEntity::Shutdown()
{
#if __BANK
	if(g_AudioEngine.GetRemoteDebug().IsDebugTypeActive(ENTITY_DEBUG))
	{
		g_AudioEngine.GetRemoteDebug().RemoveEntity(this);
	}
#endif

	if(m_ControllerEntityId != AUD_INVALID_CONTROLLER_ENTITY_ID)
	{
		AddToDeletedList();
		audController::GetController()->UnregisterEntity(this);
		m_ControllerEntityId = AUD_INVALID_CONTROLLER_ENTITY_ID;
	}

	ReleaseVariableBlock();
}

void audEntity::AddToDeletedList()
{
	if(!sm_DeletedEntities.IsFull())
	{
		sm_DeletedEntities.Append() = this;
	}
	else
	{
		// No space in the list, so process synchronously to ensure we dont leave any dangling batch requests
		for(audBatchedSoundRequestList::iterator iter = sm_BatchedSoundRequests.begin(); iter != sm_BatchedSoundRequests.end(); iter++)
		{
			if((*iter).entity == this)
			{
				(*iter).entity = NULL;
			}
		}
	}
}

void audEntity::ReportTrackerDeletion(const audTracker *tracker)
{
	if(!sm_DeletedTrackers.IsFull())
	{
		sm_DeletedTrackers.Append() = tracker;
	}
	else
	{
		// No space in the list, so process synchronously to ensure we dont leave any dangling batch requests
		for(audBatchedSoundRequestList::iterator iter = sm_BatchedSoundRequests.begin(); iter != sm_BatchedSoundRequests.end(); iter++)
		{
			if((*iter).entity && (*iter).initParams.Tracker == tracker)
			{
				(*iter).entity = NULL;
			}
		}
	}
}

bool audEntity::AllocateEntityVariableBlock(const char * mapName)
{
	return AllocateEntityVariableBlock(atStringHash(mapName));
}

bool audEntity::AllocateEntityVariableBlock(u32 mapNameHash) 
{
	for(s16 i=0; i< g_MaxAudEntityVariableBlocks; i++)
	{
		if(!sm_EntityVariableBlockRefs[i])
		{
			//Found a free variable block
			sm_EntityVariableBlockRefs[i]++;
			m_EntityVariableBlock = i;
			GetVariableBlock()->Init(mapNameHash);
			return true;
		}
	}
	Assertf(false,"Run out of entity variable blocks, this will most likely be the cause of bugs like B* 2041782");
	m_EntityVariableBlock = -1;
	return false;
}

void audEntity::SetEntityVariable(u32 nameHash, f32 value)
{
	audVariableBlock* varBlock = GetVariableBlock();
	if(varBlock)
	{
		varBlock->SetVariable(nameHash, value);
	}
}

f32 audEntity::GetEntityVariableValue(u32 nameHash)
{
	audVariableBlock* varBlock = GetVariableBlock();
	if(varBlock)
	{
		const f32* var = varBlock->FindVariableAddress(nameHash);
		if(var)
		{
			return *var;
		}
	}
	return 0.f;
}


void audEntity::StopAllSounds(bool allowRelease /* = true */)
{
	if(m_ControllerEntityId != AUD_INVALID_CONTROLLER_ENTITY_ID)
	{
		audController::GetController()->StopAllSounds(this, allowRelease);
	}
}

void audEntity::PreUpdateService(u32 UNUSED_PARAM(timeInMs))
{
}

void audEntity::PostUpdate()
{
}

void audEntity::UpdateSound(audSound* UNUSED_PARAM(sound), audRequestedSettings *UNUSED_PARAM(reqSets), u32 UNUSED_PARAM(timeInMs))
{

}

//////////////////////////////////////////////////////////////////////////

void audEntity::CreateSound_LocalReference(const char *soundName, audSound **soundRef, const audSoundInitParams *initParams  /* = NULL */)
{
	CreateSound_LocalReference(atStringHash(soundName), soundRef, initParams);
#if __BANK
	if(g_WarnOnMissingSounds && !*soundRef)
	{
		audWarningf("Missing sound %s", soundName);
	}
#endif
}

void audEntity::CreateSound_LocalReference(const u32 soundHash, audSound **soundRef, const audSoundInitParams *initParams  /* = NULL */)
{
	// ignore requests for NULL_SOUND
	if (soundHash == g_NullSoundHash || m_ControllerEntityId==AUD_INVALID_CONTROLLER_ENTITY_ID || !soundRef)
	{
		if(soundRef)
		{
			*soundRef = NULL;
		}
		return;
	}

	audSoundInitParams tmpParams;
	const audSoundInitParams *actualParams = (initParams) ? initParams : &tmpParams;
	*soundRef = audController::GetController()->GetSoundInstance(soundHash, this, NULL, actualParams);

	SetInitialSoundParams(soundRef, actualParams);
}

void audEntity::CreateSound_LocalReference(const audMetadataRef soundMetadataRef, audSound **soundRef, const audSoundInitParams *initParams)
{
	if (m_ControllerEntityId==AUD_INVALID_CONTROLLER_ENTITY_ID || !soundRef || !soundMetadataRef.IsValid())
	{
		if(soundRef)
		{
			*soundRef = NULL;
		}
		return;
	}

	audSoundInitParams tmpParams;
	const audSoundInitParams *actualParams = (initParams) ? initParams : &tmpParams;
	*soundRef = audController::GetController()->GetSoundInstance(soundMetadataRef, this, NULL, actualParams);

	SetInitialSoundParams(soundRef, actualParams);
}

void audEntity::CreateSound_PersistentReference(const audMetadataRef soundMetadataRef, audSound **soundRef, const audSoundInitParams *initParams)
{
	audAssertf(m_ControllerEntityId!=AUD_INVALID_CONTROLLER_ENTITY_ID, "Trying to play a sound from an audio entity with no controller - need to call audEntity::Init()");
	audAssertf(!*soundRef, "Non-NULL ptr passed to CreateSound_PersistentReference.  This is an ignorable assert (though it's likely that it will fire a few times in a row) and simply means that a pointer should have been set to NULL before calling this function, to ensure a valid reference isn't lost.");
	if (m_ControllerEntityId==AUD_INVALID_CONTROLLER_ENTITY_ID || !soundRef)
	{
		*soundRef = NULL;
		return;
	}

	audSoundInitParams tmpParams;
	const audSoundInitParams *actualParams = (initParams) ? initParams : &tmpParams;
	audController::GetController()->GetPersistentSoundInstance(soundMetadataRef, this, soundRef, actualParams);

	SetInitialSoundParams(soundRef, actualParams);
}

//////////////////////////////////////////////////////////////////////////

void audEntity::CreateSound_PersistentReference(const char *soundName, audSound **soundRef, const audSoundInitParams *initParams  /* = NULL */)
{
	CreateSound_PersistentReference(atStringHash(soundName), soundRef, initParams);
#if __BANK
	if(g_WarnOnMissingSounds && !*soundRef)
	{
		audWarningf("Missing sound %s", soundName);
	}
#endif
}

void audEntity::CreateSound_PersistentReference(const u32 soundHash, audSound **soundRef, const audSoundInitParams *initParams  /* = NULL */)
{
	audAssertf(m_ControllerEntityId!=AUD_INVALID_CONTROLLER_ENTITY_ID, "Trying to play a sound from an audio entity with no controller - need to call audEntity::Init()");
	audAssertf(!*soundRef, "Non-NULL ptr passed to CreateSound_PersistentReference.  This is an ignorable assert (though it's likely that it will fire a few times in a row) and simply means that a pointer should have been set to NULL before calling this function, to ensure a valid reference isn't lost.");
	// ignore requests for null sound
	if (soundHash == g_NullSoundHash || m_ControllerEntityId==AUD_INVALID_CONTROLLER_ENTITY_ID || !soundRef)
	{
		*soundRef = NULL;
		return;
	}

	audSoundInitParams tmpParams;
	const audSoundInitParams *actualParams = (initParams) ? initParams : &tmpParams;
	audController::GetController()->GetPersistentSoundInstance(soundHash, this, soundRef, actualParams);

	SetInitialSoundParams(soundRef, actualParams);
}

//////////////////////////////////////////////////////////////////////////

bool audEntity::CreateAndPlaySound(const char *soundName, const audSoundInitParams *initParams /* = NULL */)
{
	const bool success = CreateAndPlaySound(atStringHash(soundName), initParams);
#if __BANK
	if(g_WarnOnMissingSounds && !success)
	{
		audWarningf("Missing sound %s", soundName);
	}
#endif
	return success;
}

bool audEntity::CreateAndPlaySound(const u32 soundHash, const audSoundInitParams *initParams  /* = NULL */)
{
	return CreateAndPlaySound(SOUNDFACTORY.GetMetadataManager().GetObjectMetadataRefFromHash(soundHash), initParams);
}

bool audEntity::CreateAndPlaySound(const audMetadataRef metadataRef, const audSoundInitParams *initParams /* = NULL */)
{
#if __ASSERT
	if(initParams)
	{
		Assert(initParams->VolumeCurveScale != 0.f);
	}
#endif

#if AUD_ENABLE_BATCHING
	return AddBatchedSoundRequest(metadataRef, initParams);
#else
	audSoundInitParams tmpParams;
	const audSoundInitParams *actualParams = (initParams) ? initParams : &tmpParams;

	audSound *sound;
	CreateSound_LocalReference(metadataRef, &sound, actualParams);
	if (!sound)
	{
		return false;
	}

	audSoundInitParams *nonConstParams = const_cast<audSoundInitParams*>(actualParams);
	sound->PrepareAndPlay(const_cast<audWaveSlot*>(actualParams->WaveSlot), nonConstParams->AllowLoad, nonConstParams->PrepareTimeLimit);
	return true;
#endif
}

bool audEntity::AddBatchedSoundRequest(audMetadataRef soundMetadataRef, const audSoundInitParams *initParams)
{
#if __USEDEBUGAUDIO	
	if(initParams)
	{
		if(initParams->EnvironmentGroup || initParams->Tracker)
		{
			g_Controller->GetThreadVerifier()->OnCreateSound(soundMetadataRef);	
		}
		Assertf(initParams->Volume >= g_SilenceVolume*3.f, "Invalid requested volume: %f", initParams->Volume);
		Assertf(initParams->Volume <= 24.f,"Invalid requested volume: %f", initParams->Volume);
	}
#endif // RSG_ASSERT

#if RSG_BANK
	if(audSoundManager::ShouldBreakOnCreate(soundMetadataRef, this))
	{
		audWarningf("Debug breaking on batched sound %s creation", this->GetName());
		__debugbreak();
	}

	if(sm_BatchedSoundRequests.IsFull() && !sm_PrintedFullBatchList)
	{
		sm_PrintedFullBatchList = true;
		for(s32 i = 0; i < sm_BatchedSoundRequests.GetCount(); i++)
		{
			const char *soundName;
			audMetadataObjectInfo objInfo;
			if(g_AudioEngine.GetSoundManager().GetFactory().GetMetadataManager().GetObjectInfo(sm_BatchedSoundRequests[i].soundMetadataRef, objInfo))
			{
				soundName = objInfo.GetName_Debug();
			}
			else
			{
				soundName = "InvalidSoundRef";
			}

			const char *entityName = "(no entity)";

			const bool trackerHasBeenDeleted = (initParams->Tracker != NULL) && (sm_DeletedTrackers.Find(initParams->Tracker) != -1);
			const bool entityHasBeenDeleted = sm_BatchedSoundRequests[i].entity == NULL ||
				(sm_DeletedEntities.Find(sm_BatchedSoundRequests[i].entity) != -1) ||
				trackerHasBeenDeleted;

			if(!entityHasBeenDeleted && sm_BatchedSoundRequests[i].entity)
			{
				entityName = sm_BatchedSoundRequests[i].entity->GetEntityName();
			}
			audWarningf("%d: %s (predelay: %d) / %s %p", i, soundName, sm_BatchedSoundRequests[i].initParams.Predelay, entityName, sm_BatchedSoundRequests[i].entity);
		}

		audAssertf(!sm_BatchedSoundRequests.IsFull(), "BatchedSoundList is full");
	}
#endif // RSG_BANK

	if(soundMetadataRef != g_NullSoundRef && soundMetadataRef.IsValid() && !sm_BatchedSoundRequests.IsFull())
	{
		audBatchedSoundRequest &req = sm_BatchedSoundRequests.Append();
		req.entity = this;
		req.soundMetadataRef = soundMetadataRef;
		if(initParams)
		{
			audAssertf(initParams->Predelay < 10000, "Large predelay requested in AddBatchedSoundRequest: %u", initParams->Predelay);

			req.initParams = *initParams;
			if(initParams->Tracker)
			{
				// Ensure we get a ReportTrackerDeletion call if the entity that owns this tracker is deleted
				initParams->Tracker->IncrementReferenceCount();
			}
			
			// For environmentgroups that track the life of the sound (initialized with NULL audEntity)
			// we need to make sure that group isn't deleted between the batched sound request and when it's processed
			// so add a reference to the group here and then decrement it when it's processed
			if(initParams->EnvironmentGroup)
			{
				const_cast<audEnvironmentGroupInterface*>(initParams->EnvironmentGroup)->AddSoundReference();
			}
		}
		else
		{
			audSoundInitParams tempInitParams;
			req.initParams = tempInitParams;
		}
		return true;
	}
	return false;
}

void audEntity::ProcessBatchedSoundRequests(const s32 timeStepMs)
{
	for(s32 i = sm_BatchedSoundRequests.GetCount() - 1; i >= 0; i--)
	{
		audBatchedSoundRequest &req = sm_BatchedSoundRequests[i];
		audSoundInitParams *initParams = &sm_BatchedSoundRequests[i].initParams;
	
		const bool trackerHasBeenDeleted = (initParams->Tracker != NULL) && (sm_DeletedTrackers.Find(initParams->Tracker) != -1);
		const bool entityHasBeenDeleted = req.entity == NULL ||
										  (sm_DeletedEntities.Find(req.entity) != -1) ||
										  trackerHasBeenDeleted;
		if(!entityHasBeenDeleted)
		{
			if(initParams->Predelay > 100)
			{
				// Decrement the predelay but leave the request in the list
				initParams->Predelay -= Min(initParams->Predelay, timeStepMs);
				continue;
			}
			audSound *sound;
			req.entity->CreateSound_LocalReference(req.soundMetadataRef, &sound, initParams);
			if(sound)
			{
				// TODO: set a flag to assert if looping (have to wait until prepared)
				sound->PrepareAndPlay(const_cast<audWaveSlot*>(initParams->WaveSlot), initParams->AllowLoad, initParams->PrepareTimeLimit);
			}
		}
		
		if(initParams->EnvironmentGroup)
		{
			// Decrement the envgroup reference we added when creating the batched sound request
			const_cast<audEnvironmentGroupInterface*>(initParams->EnvironmentGroup)->RemoveSoundReference();
		}

		if(initParams->Tracker && !trackerHasBeenDeleted)
		{
			// Remove the extra reference we added to this tracker while it was sitting in the batched list
			initParams->Tracker->DecrementReferenceCount();
		}
		// remove this request from the list since we've either played it or it's entity has been deleted
		sm_BatchedSoundRequests.DeleteFast(i);
	}
}

void audEntity::ResetDeletedObjectLists()
{
	sm_DeletedEntities.Reset();
	sm_DeletedTrackers.Reset();
}

//////////////////////////////////////////////////////////////////////////

void audEntity::CreateAndPlaySound_Persistent(const char *soundName, audSound **soundRef, const audSoundInitParams *initParams  /* = NULL */)
{
	CreateAndPlaySound_Persistent(atStringHash(soundName), soundRef, initParams);
#if __BANK
	if(g_WarnOnMissingSounds && !*soundRef)
	{
		audWarningf("Missing sound %s", soundName);
	}
#endif
}

void audEntity::CreateAndPlaySound_Persistent(const u32 soundHash, audSound **soundRef, const audSoundInitParams *initParams  /* = NULL */)
{
	audSoundInitParams tmpParams;
	const audSoundInitParams *actualParams = (initParams) ? initParams : &tmpParams;

	CreateSound_PersistentReference(soundHash, soundRef, actualParams);
	audSound* RESTRICT soundPtr = *soundRef;
	if (!soundPtr)
	{
		*soundRef = NULL;
		return;
	}

	audSoundInitParams *NonConstParams = const_cast<audSoundInitParams*>(actualParams);
	soundPtr->PrepareAndPlay(const_cast<audWaveSlot*>(actualParams->WaveSlot), NonConstParams->AllowLoad, NonConstParams->PrepareTimeLimit);
}

void audEntity::CreateAndPlaySound_Persistent(const audMetadataRef metadataRef, audSound **soundRef, const audSoundInitParams *initParams  /* = NULL */)
{
	audSoundInitParams tmpParams;
	const audSoundInitParams *actualParams = (initParams) ? initParams : &tmpParams;

	CreateSound_PersistentReference(metadataRef, soundRef, actualParams);
	audSound* RESTRICT soundPtr = *soundRef;
	if (!soundPtr)
	{
		*soundRef = NULL;
		return;
	}

	audSoundInitParams *NonConstParams = const_cast<audSoundInitParams*>(actualParams);
	soundPtr->PrepareAndPlay(const_cast<audWaveSlot*>(actualParams->WaveSlot), NonConstParams->AllowLoad, NonConstParams->PrepareTimeLimit);
}

//////////////////////////////////////////////////////////////////////////

void audEntity::SetInitialSoundParams(audSound **soundRef, const audSoundInitParams *initParams)
{
	audSound* RESTRICT soundPtr = *soundRef;
	if (!soundPtr)
	{
		return;
	}

	audRequestedSettings* RESTRICT pReqSets = soundPtr->GetRequestedSettings();
	TrapZ((size_t)pReqSets);

	audAssertf(initParams->StartOffset >= 0, "Invalid start offset: %d", initParams->StartOffset);
	if(initParams->IsStartOffsetPercentage)
	{
		audAssertf(initParams->StartOffset >= 0 && initParams->StartOffset <= 100, "Invalid percentage start offset: %d", initParams->StartOffset);
	}
	pReqSets->SetUpdateEntity(initParams->UpdateEntity);
	pReqSets->SetVolume(initParams->Volume);
	pReqSets->SetVolumeCurveScale(initParams->VolumeCurveScale);
	pReqSets->SetPitch(initParams->Pitch);
	pReqSets->SetLowPassFilterCutoff(initParams->LPFCutoff);
	pReqSets->SetHighPassFilterCutoff(initParams->HPFCutoff);
	pReqSets->SetPan(initParams->Pan);
	pReqSets->SetAllowOrphaned(initParams->AllowOrphaned);
	pReqSets->SetVirtualisationScoreOffset(initParams->VirtualisationScoreOffset);
	pReqSets->SetSpeakerMask(initParams->SpeakerMask);
	soundPtr->SetStartOffset(initParams->StartOffset, initParams->IsStartOffsetPercentage);
	pReqSets->SetPosition(VECTOR3_TO_VEC3V(initParams->Position));
	pReqSets->SetPostSubmixVolumeAttenuation(initParams->PostSubmixAttenuation);

	for(u32 i = 0; i < audSoundInitParams::kMaxInitParamVariables; i++)
	{
		if(initParams->Variables[i].nameHash != 0)
		{
			soundPtr->FindAndSetVariableValue(initParams->Variables[i].nameHash, initParams->Variables[i].value);
		}
		else
		{
			break; 
		}
	}

	// Don't do anything with occlusion groups, as these are now set in audSound::Init(), and will assert due to reference counting if ever done again
	// Now set occlusion groups from here, so that they're only set if the sound was successfully Init'd
	pReqSets->SetEnvironmentGroup(const_cast<audEnvironmentGroupInterface*>(initParams->EnvironmentGroup));
	pReqSets->SetClientVariable(initParams->u32ClientVar);
}

//////////////////////////////////////////////////////////////////////////

void audEntity::StopAndForgetSoundsPrivate(audSound* sound1, audSound* sound2, audSound* sound3, audSound* sound4, 
						 audSound* sound5, audSound* sound6, audSound* sound7, audSound* sound8, 
						 audSound* sound9, audSound* sound10, audSound* sound11, audSound* sound12, bool continueUpdatingEntity)
{
	if (sound1)
	{
		sound1->StopAndForget(continueUpdatingEntity);
	}
	if (sound2)
	{
		sound2->StopAndForget(continueUpdatingEntity);
	}
	if (sound3)
	{
		sound3->StopAndForget(continueUpdatingEntity);
	}
	if (sound4)
	{
		sound4->StopAndForget(continueUpdatingEntity);
	}
	if (sound5)
	{
		sound5->StopAndForget(continueUpdatingEntity);
	}
	if (sound6)
	{
		sound6->StopAndForget(continueUpdatingEntity);
	}
	if (sound7)
	{
		sound7->StopAndForget(continueUpdatingEntity);
	}
	if (sound8)
	{
		sound8->StopAndForget(continueUpdatingEntity);
	}
	if (sound9)
	{
		sound9->StopAndForget(continueUpdatingEntity);
	}
	if (sound10)
	{
		sound10->StopAndForget(continueUpdatingEntity);
	}
	if (sound11)
	{
		sound11->StopAndForget(continueUpdatingEntity);
	}
	if (sound12)
	{
		sound12->StopAndForget(continueUpdatingEntity);
	}
}

} // namespace rage
