/* * Copyright (c) <2017> Side Effects Software Inc. * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in all * copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE * SOFTWARE. * * Produced by: * Mykola Konyk * Side Effects Software Inc * 123 Front Street West, Suite 1401 * Toronto, Ontario * Canada M5J 2M2 * 416-504-9876 * */ #include "HoudiniEngineScheduler.h" #include "HoudiniApi.h" #include "HoudiniEngineRuntimePrivatePCH.h" #include "HoudiniEngineUtils.h" #include "HoudiniEngine.h" #include "HoudiniAsset.h" #include "HoudiniEngineString.h" #include "Misc/ScopeLock.h" const uint32 FHoudiniEngineScheduler::InitialTaskSize = 256u; const float FHoudiniEngineScheduler::UpdateFrequency = 0.1f; FHoudiniEngineScheduler::FHoudiniEngineScheduler() : Tasks( nullptr ) , PositionWrite( 0u ) , PositionRead( 0u ) , bStopping( false ) { // Make sure size is power of two. TaskCount = FPlatformMath::RoundUpToPowerOfTwo( FHoudiniEngineScheduler::InitialTaskSize ); if ( TaskCount ) { // Allocate buffer to store all tasks. Tasks = static_cast< FHoudiniEngineTask * >( FMemory::Malloc( TaskCount * sizeof( FHoudiniEngineTask ) ) ); if ( Tasks ) { // Zero memory. FMemory::Memset( Tasks, 0x0, TaskCount * sizeof( FHoudiniEngineTask ) ); } } } FHoudiniEngineScheduler::~FHoudiniEngineScheduler() { if ( TaskCount ) { FMemory::Free( Tasks ); Tasks = nullptr; } } void FHoudiniEngineScheduler::TaskDescription( FHoudiniEngineTaskInfo & TaskInfo, const FString & ActorName, const FString & StatusString ) { FFormatNamedArguments Args; if ( !ActorName.IsEmpty() ) { Args.Add( TEXT( "AssetName" ), FText::FromString( ActorName ) ); Args.Add( TEXT( "AssetStatus" ), FText::FromString( StatusString ) ); TaskInfo.StatusText = FText::Format( NSLOCTEXT( "TaskDescription", "TaskDescriptionProgress", "({AssetName}) : ({AssetStatus})"), Args ); } else { Args.Add( TEXT( "AssetStatus" ), FText::FromString( StatusString ) ); TaskInfo.StatusText = FText::Format( NSLOCTEXT( "TaskDescription", "TaskDescriptionProgress", "({AssetStatus})"), Args ); } } void FHoudiniEngineScheduler::TaskInstantiateAsset( const FHoudiniEngineTask & Task ) { FString AssetN; FHoudiniEngineString( Task.AssetHapiName ).ToFString( AssetN ); HOUDINI_LOG_MESSAGE( TEXT( "HAPI Asynchronous Instantiation Started for %s: Asset=%s, HoudiniAsset = 0x%x" ), *Task.ActorName, *AssetN, Task.Asset.Get() ); if ( !FHoudiniEngineUtils::IsInitialized() ) { HOUDINI_LOG_ERROR( TEXT( "TaskInstantiateAsset failed for %s: %s" ), *Task.ActorName, *FHoudiniEngineUtils::GetErrorDescription( HAPI_RESULT_NOT_INITIALIZED ) ); AddResponseMessageTaskInfo( HAPI_RESULT_NOT_INITIALIZED, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::FinishedInstantiationWithErrors, -1, Task, TEXT( "HAPI is not initialized." ) ); return; } if ( !Task.Asset.IsValid() ) { // Asset is no longer valid, return. AddResponseMessageTaskInfo( HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::FinishedInstantiationWithErrors, -1, Task, TEXT( "Asset is no longer valid." ) ); return; } if ( Task.AssetHapiName < 0 ) { // Asset is no longer valid, return. AddResponseMessageTaskInfo( HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::FinishedInstantiationWithErrors, -1, Task, TEXT( "Asset name is invalid." ) ); return; } HAPI_Result Result = HAPI_RESULT_SUCCESS; int32 AssetCount = 0; HAPI_NodeId AssetId = -1; std::string AssetNameString; double LastUpdateTime; FHoudiniEngineString HoudiniEngineString( Task.AssetHapiName ); if ( HoudiniEngineString.ToStdString( AssetNameString ) ) { // Translate asset name into Unreal string. FString AssetName = ANSI_TO_TCHAR( AssetNameString.c_str() ); // Initialize last update time. LastUpdateTime = FPlatformTime::Seconds(); // We instantiate without cooking. Result = FHoudiniApi::CreateNode( FHoudiniEngine::Get().GetSession(), -1, &AssetNameString[ 0 ], nullptr, false, &AssetId ); if ( Result != HAPI_RESULT_SUCCESS ) { AddResponseMessageTaskInfo( Result, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::FinishedInstantiationWithErrors, -1, Task, TEXT( "Error instantiating asset." ) ); return; } // Add processing notification. FHoudiniEngineTaskInfo TaskInfo( HAPI_RESULT_SUCCESS, -1, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::Processing ); TaskInfo.bLoadedComponent = Task.bLoadedComponent; TaskDescription( TaskInfo, Task.ActorName, TEXT( "Started Instantiation" ) ); FHoudiniEngine::Get().AddTaskInfo( Task.HapiGUID, TaskInfo ); // We need to spin until instantiation is finished. while( true ) { int Status = HAPI_STATE_STARTING_COOK; HOUDINI_CHECK_ERROR( &Result, FHoudiniApi::GetStatus( FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status ) ); if ( Status == HAPI_STATE_READY ) { // Cooking has been successful. AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::FinishedInstantiation, AssetId, Task, TEXT( "Finished Instantiation." ) ); break; } else if ( Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS ) { // There was an error while instantiating. FString CookResultString = FHoudiniEngineUtils::GetCookResult(); int32 CookResult = static_cast(HAPI_RESULT_SUCCESS); FHoudiniApi::GetStatus( FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_RESULT, &CookResult ); AddResponseMessageTaskInfo( static_cast(CookResult), EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::FinishedInstantiationWithErrors, AssetId, Task, FString::Printf(TEXT( "Finished Instantiation with Errors: %s" ), *CookResultString )); break; } static const double NotificationUpdateFrequency = 0.5; if ( ( FPlatformTime::Seconds() - LastUpdateTime ) >= NotificationUpdateFrequency ) { // Reset update time. LastUpdateTime = FPlatformTime::Seconds(); const FString& CookStateMessage = FHoudiniEngineUtils::GetCookState(); AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::Processing, AssetId, Task, CookStateMessage ); } // We want to yield. FPlatformProcess::Sleep( UpdateFrequency ); } } else { AddResponseMessageTaskInfo( HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetInstantiation, EHoudiniEngineTaskState::FinishedInstantiationWithErrors, -1, Task, TEXT( "Error retrieving asset name." ) ); return; } } void FHoudiniEngineScheduler::TaskCookAsset( const FHoudiniEngineTask & Task ) { if ( !FHoudiniEngineUtils::IsInitialized() ) { HOUDINI_LOG_ERROR( TEXT( "TaskCookAsset failed for %s: %s"), *Task.ActorName, *FHoudiniEngineUtils::GetErrorDescription( HAPI_RESULT_NOT_INITIALIZED ) ); AddResponseMessageTaskInfo( HAPI_RESULT_NOT_INITIALIZED, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::FinishedCookingWithErrors, -1, Task, TEXT( "HAPI is not initialized." ) ); return; } // Retrieve asset id. HAPI_NodeId AssetId = Task.AssetId; HAPI_Result Result = HAPI_RESULT_SUCCESS; HOUDINI_LOG_MESSAGE( TEXT( "HAPI Asynchronous Cooking Started for %s., AssetId = %d" ), *Task.ActorName, AssetId ); if ( AssetId == -1 ) { // We have an invalid asset id. HOUDINI_LOG_ERROR( TEXT( "TaskCookAsset failed for %s: Invalid Asset Id." ), *Task.ActorName ); AddResponseMessageTaskInfo( HAPI_RESULT_FAILURE, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::FinishedCookingWithErrors, -1, Task, TEXT( "Asset has invalid id." ) ); return; } Result = FHoudiniApi::CookNode( FHoudiniEngine::Get().GetSession(), AssetId, nullptr ); if ( Result != HAPI_RESULT_SUCCESS ) { AddResponseMessageTaskInfo( Result, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::FinishedCookingWithErrors, AssetId, Task, TEXT( "Error cooking asset." ) ); return; } // Add processing notification. AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::Processing, AssetId, Task, TEXT( "Started Cooking" ) ); // Initialize last update time. double LastUpdateTime = FPlatformTime::Seconds(); // We need to spin until cooking is finished. while ( true ) { int32 Status = HAPI_STATE_STARTING_COOK; HOUDINI_CHECK_ERROR( &Result, FHoudiniApi::GetStatus( FHoudiniEngine::Get().GetSession(), HAPI_STATUS_COOK_STATE, &Status ) ); if ( Status == HAPI_STATE_READY ) { // Cooking has been successful. AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::FinishedCooking, AssetId, Task, TEXT( "Finished Cooking" ) ); break; } else if ( Status == HAPI_STATE_READY_WITH_FATAL_ERRORS || Status == HAPI_STATE_READY_WITH_COOK_ERRORS ) { // There was an error while instantiating. AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::FinishedCookingWithErrors, AssetId, Task, TEXT( "Finished Cooking with Errors" ) ); break; } static const double NotificationUpdateFrequency = 0.5; if ( FPlatformTime::Seconds() - LastUpdateTime >= NotificationUpdateFrequency ) { // Reset update time. LastUpdateTime = FPlatformTime::Seconds(); // Retrieve status string. const FString & CookStateMessage = FHoudiniEngineUtils::GetCookState(); AddResponseMessageTaskInfo( HAPI_RESULT_SUCCESS, EHoudiniEngineTaskType::AssetCooking, EHoudiniEngineTaskState::Processing, AssetId, Task, CookStateMessage ); } // We want to yield. FPlatformProcess::Sleep( UpdateFrequency ); } } void FHoudiniEngineScheduler::TaskDeleteAsset( const FHoudiniEngineTask & Task ) { HOUDINI_LOG_MESSAGE( TEXT( "HAPI Asynchronous Destruction Started for %s. " ) TEXT( "AssetId = %d" ), *Task.ActorName, Task.AssetId ); if ( FHoudiniEngineUtils::IsHoudiniNodeValid( Task.AssetId ) ) FHoudiniEngineUtils::DestroyHoudiniAsset( Task.AssetId ); // We do not insert task info as this is a fire and forget operation. // At this point component most likely does not exist. } void FHoudiniEngineScheduler::AddResponseTaskInfo( HAPI_Result Result, EHoudiniEngineTaskType::Type TaskType, EHoudiniEngineTaskState::Type TaskState, HAPI_NodeId AssetId, const FHoudiniEngineTask & Task ) { FHoudiniEngineTaskInfo TaskInfo( Result, AssetId, TaskType, TaskState ); FString StatusString = FHoudiniEngineUtils::GetErrorDescription(); TaskInfo.bLoadedComponent = Task.bLoadedComponent; TaskDescription( TaskInfo, Task.ActorName, StatusString ); FHoudiniEngine::Get().AddTaskInfo( Task.HapiGUID, TaskInfo ); } void FHoudiniEngineScheduler::AddResponseMessageTaskInfo( HAPI_Result Result, EHoudiniEngineTaskType::Type TaskType, EHoudiniEngineTaskState::Type TaskState, HAPI_NodeId AssetId, const FHoudiniEngineTask & Task, const FString & ErrorMessage ) { FHoudiniEngineTaskInfo TaskInfo( Result, AssetId, TaskType, TaskState ); TaskInfo.bLoadedComponent = Task.bLoadedComponent; TaskDescription( TaskInfo, Task.ActorName, ErrorMessage ); FHoudiniEngine::Get().AddTaskInfo( Task.HapiGUID, TaskInfo ); } void FHoudiniEngineScheduler::ProcessQueuedTasks() { while( !bStopping ) { while ( true ) { FHoudiniEngineTask Task; { FScopeLock ScopeLock( &CriticalSection ); // We have no tasks left. if ( PositionWrite == PositionRead ) break; // Retrieve task. Task = Tasks[ PositionRead ]; PositionRead++; // Wrap around if required. PositionRead &= ( TaskCount - 1 ); } bool bTaskProcessed = true; switch ( Task.TaskType ) { case EHoudiniEngineTaskType::AssetInstantiation: { TaskInstantiateAsset( Task ); break; } case EHoudiniEngineTaskType::AssetCooking: { TaskCookAsset( Task ); break; } case EHoudiniEngineTaskType::AssetDeletion: { TaskDeleteAsset( Task ); break; } default: { bTaskProcessed = false; break; } } if ( !bTaskProcessed ) break; } if ( FPlatformProcess::SupportsMultithreading() ) { // We want to yield for a bit. FPlatformProcess::Sleep( UpdateFrequency ); } else { // If we are running in single threaded mode, return so we don't block everything else. return; } } } void FHoudiniEngineScheduler::AddTask( const FHoudiniEngineTask & Task ) { FScopeLock ScopeLock( &CriticalSection ); // Check if we need to grow our circular buffer. if ( PositionWrite + 1 == PositionRead ) { // Calculate next size (next power of two). uint32 NextTaskCount = FPlatformMath::RoundUpToPowerOfTwo( TaskCount + 1 ); // Allocate new buffer. FHoudiniEngineTask * Buffer = static_cast< FHoudiniEngineTask * >( FMemory::Malloc( NextTaskCount * sizeof( FHoudiniEngineTask ) ) ); if( !Buffer ) return; // Zero memory. FMemory::Memset( Buffer, 0x0, NextTaskCount * sizeof( FHoudiniEngineTask ) ); // Copy elements from old buffer to new one. if ( PositionRead < PositionWrite ) { FMemory::Memcpy( Buffer, Tasks + PositionRead, sizeof( FHoudiniEngineTask ) * ( PositionWrite - PositionRead ) ); // Update index positions. PositionRead = 0; PositionWrite = PositionWrite - PositionRead; } else { FMemory::Memcpy( Buffer, Tasks + PositionRead, sizeof( FHoudiniEngineTask ) * ( TaskCount - PositionRead ) ); FMemory::Memcpy( Buffer + TaskCount - PositionRead, Tasks, sizeof( FHoudiniEngineTask ) * PositionWrite ); // Update index positions. PositionRead = 0; PositionWrite = TaskCount - PositionRead + PositionWrite; } // Deallocate old buffer. FMemory::Free( Tasks ); // Bookkeeping. Tasks = Buffer; TaskCount = NextTaskCount; } // Store task. Tasks[ PositionWrite ] = Task; PositionWrite++; // Wrap around if required. PositionWrite &= ( TaskCount - 1 ); } uint32 FHoudiniEngineScheduler::Run() { ProcessQueuedTasks(); return 0; } void FHoudiniEngineScheduler::Stop() { bStopping = true; } void FHoudiniEngineScheduler::Tick() { ProcessQueuedTasks(); } FSingleThreadRunnable * FHoudiniEngineScheduler::GetSingleThreadInterface() { return this; }