/// Begin asynchronous loading of an object. /// /// @param[in] path GameObject path. /// /// @return ID for the load request if started successfully, invalid index if not. /// /// @see TryFinishLoad(), FinishLoad() size_t GameObjectLoader::BeginLoadObject( GameObjectPath path ) { // Search for an existing load request with the given path. ConcurrentHashMap< GameObjectPath, LoadRequest* >::ConstAccessor requestConstAccessor; if( m_loadRequestMap.Find( requestConstAccessor, path ) ) { LoadRequest* pRequest = requestConstAccessor->Second(); HELIUM_ASSERT( pRequest ); AtomicIncrementRelease( pRequest->requestCount ); // We can release now, as the request shouldn't get released now that we've incremented its reference count. requestConstAccessor.Release(); return m_loadRequestPool.GetIndex( pRequest ); } // Get the package loader to use for the given object. PackageLoader* pPackageLoader = GetPackageLoader( path ); if( !pPackageLoader ) { HELIUM_TRACE( TRACE_ERROR, TXT( "GameObjectLoader::BeginLoadObject(): Failed to locate package loader for \"%s\".\n" ), *path.ToString() ); return Invalid< size_t >(); } // Add the load request. LoadRequest* pRequest = m_loadRequestPool.Allocate(); pRequest->path = path; HELIUM_ASSERT( !pRequest->spObject ); pRequest->pPackageLoader = pPackageLoader; SetInvalid( pRequest->packageLoadRequestId ); HELIUM_ASSERT( pRequest->linkTable.IsEmpty() ); pRequest->stateFlags = 0; pRequest->requestCount = 1; ConcurrentHashMap< GameObjectPath, LoadRequest* >::Accessor requestAccessor; if( m_loadRequestMap.Insert( requestAccessor, KeyValue< GameObjectPath, LoadRequest* >( path, pRequest ) ) ) { // New load request was created, so tick it once to get the load process running. requestAccessor.Release(); TickLoadRequest( pRequest ); } else { // A matching request was added while we were building our request, so reuse it. m_loadRequestPool.Release( pRequest ); pRequest = requestAccessor->Second(); HELIUM_ASSERT( pRequest ); AtomicIncrementRelease( pRequest->requestCount ); // We can release now, as the request shouldn't get released now that we've incremented its reference count. requestAccessor.Release(); } return m_loadRequestPool.GetIndex( pRequest ); }
/// Initialize this manager. /// /// @return True if this manager was initialized successfully, false if not. /// /// @see Shutdown() bool WorldManager::Initialize() { HELIUM_ASSERT( !m_spWorldPackage ); // Create the world package first. // XXX TMC: Note that we currently assume that the world package has no parents, so we don't need to handle // recursive package creation. If we want to move the world package to a subpackage, this will need to be // updated accordingly. GameObjectPath worldPackagePath = GetWorldPackagePath(); HELIUM_ASSERT( !worldPackagePath.IsEmpty() ); HELIUM_ASSERT( worldPackagePath.GetParent().IsEmpty() ); bool bCreateResult = GameObject::Create< Package >( m_spWorldPackage, worldPackagePath.GetName(), NULL ); HELIUM_ASSERT( bCreateResult ); if( !bCreateResult ) { HELIUM_TRACE( TRACE_ERROR, TXT( "WorldManager::Initialize(): Failed to create world package \"%s\".\n" ), *worldPackagePath.ToString() ); return false; } HELIUM_ASSERT( m_spWorldPackage ); // Reset frame timings. m_actualFrameTickCount = 0; m_frameTickCount = 0; m_frameDeltaTickCount = 0; m_frameDeltaSeconds = 0.0f; // First frame still needs to be processed. m_bProcessedFirstFrame = false; return true; }
/// @copydoc ResourceHandler::CacheResource() bool ShaderResourceHandler::CacheResource( ObjectPreprocessor* pObjectPreprocessor, Resource* pResource, const String& rSourceFilePath ) { HELIUM_ASSERT( pObjectPreprocessor ); HELIUM_ASSERT( pResource ); const Shader* pShader = Reflect::AssertCast< const Shader >( pResource ); GameObjectPath shaderPath = pShader->GetPath(); HELIUM_TRACE( TraceLevels::Info, TXT( "ShaderResourceHandler: Caching \"%s\".\n" ), *shaderPath.ToString() ); DefaultAllocator allocator; FileStream* pSourceFileStream = FileStream::OpenFileStream( rSourceFilePath, FileStream::MODE_READ ); if( !pSourceFileStream ) { HELIUM_TRACE( TraceLevels::Error, TXT( "ShaderResourceHandler: Source file for shader resource \"%s\" failed to open properly.\n" ), *shaderPath.ToString() ); return false; } // Load the entire shader resource into memory. int64_t size64 = pSourceFileStream->GetSize(); HELIUM_ASSERT( size64 != -1 ); HELIUM_ASSERT( static_cast< uint64_t >( size64 ) <= static_cast< size_t >( -1 ) ); if( size64 > static_cast< uint64_t >( static_cast< size_t >( -1 ) ) ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "ShaderResourceHandler: Source file for shader resource \"%s\" is too large to fit into " ) TXT( "memory for preprocessing.\n" ) ), *shaderPath.ToString() ); delete pSourceFileStream; return false; } size_t size = static_cast< size_t >( size64 ); void* pShaderData = allocator.Allocate( size ); HELIUM_ASSERT( pShaderData ); if( !pShaderData ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "ShaderResourceHandler: Failed to allocate %" ) TPRIuSZ TXT( " bytes for loading the source " ) TXT( "data of \"%s\" for preprocessing.\n" ) ), size, *shaderPath.ToString() ); delete pSourceFileStream; return false; } BufferedStream( pSourceFileStream ).Read( pShaderData, 1, size ); delete pSourceFileStream; // Parse all preprocessor toggle and selection tokens from the shader source. Shader::PersistentResourceData resourceData; const char* pLineEnd = static_cast< const char* >( pShaderData ); const char* pShaderEnd = pLineEnd + size; do { const char* pLineStart = pLineEnd; while( pLineEnd < pShaderEnd ) { char character = *pLineEnd; if( character == '\n' || character == '\r' ) { break; } ++pLineEnd; } ParseLine( shaderPath, resourceData, pLineStart, pLineEnd ); while( pLineEnd < pShaderEnd ) { char character = *pLineEnd; if( character != '\n' && character != '\r' ) { break; } ++pLineEnd; } } while( pLineEnd < pShaderEnd ); allocator.Free( pShaderData ); // Serialize the persistent shader resource data for each platform. for( size_t platformIndex = 0; platformIndex < static_cast< size_t >( Cache::PLATFORM_MAX ); ++platformIndex ) { PlatformPreprocessor* pPreprocessor = pObjectPreprocessor->GetPlatformPreprocessor( static_cast< Cache::EPlatform >( platformIndex ) ); if( !pPreprocessor ) { continue; } Resource::PreprocessedData& rPreprocessedData = pResource->GetPreprocessedData( static_cast< Cache::EPlatform >( platformIndex ) ); SaveObjectToPersistentDataBuffer(&resourceData, rPreprocessedData.persistentDataBuffer); rPreprocessedData.subDataBuffers.Resize( 0 ); rPreprocessedData.bLoaded = true; } return true; }
/// Parse the given shader source line for toggle and select options. /// /// @param[in] shaderPath GameObject path of the shader resource being preprocessed (used for logging purposes /// only). /// @param[in] rResourceData Persistent shader resource data to update. /// @param[in] pLineStart Pointer to the first character in the line. /// @param[in] pLineEnd Pointer to the character just past the end of the line. void ShaderResourceHandler::ParseLine( GameObjectPath shaderPath, Shader::PersistentResourceData& rResourceData, const char* pLineStart, const char* pLineEnd ) { HELIUM_UNREF( shaderPath ); // Not used if logging is disabled. HELIUM_ASSERT( pLineStart ); HELIUM_ASSERT( pLineEnd >= pLineStart ); const char linePrefix[] = "//!"; const char toggleUserCommand[] = "@toggle"; const char selectUserCommand[] = "@select"; const char toggleSystemCommand[] = "@systoggle"; const char selectSystemCommand[] = "@sysselect"; size_t characterCount = static_cast< size_t >( pLineEnd - pLineStart ); // Only process lines that start with the special comment prefix. if( characterCount < HELIUM_ARRAY_COUNT( linePrefix ) - 1 || CompareString( pLineStart, linePrefix, HELIUM_ARRAY_COUNT( linePrefix ) - 1 ) != 0 ) { return; } pLineStart += HELIUM_ARRAY_COUNT( linePrefix ) - 1; characterCount -= HELIUM_ARRAY_COUNT( linePrefix ) - 1; // Split the line based on groups of whitespaces. CharString line( pLineStart, characterCount ); DynamicArray< CharString > splitLine; line.Split( splitLine, " \t\v\f", Invalid< size_t >(), true ); // Ignore the first split if it's empty (will occur if the command is preceded by whitespaces). size_t splitCount = splitLine.GetSize(); if( splitCount > 0 && splitLine[ 0 ].IsEmpty() ) { splitLine.Remove( 0 ); --splitCount; } // We need at least 2 splits (command and at least one command parameter). if( splitCount < 2 ) { return; } // Process the command. DynamicArray< CharString > splitCommand; splitLine[ 0 ].Split( splitCommand, '_' ); size_t commandSplitCount = splitCommand.GetSize(); if( commandSplitCount < 1 || commandSplitCount > 2 ) { // Invalid command format. return; } const CharString& rCommand = splitCommand[ 0 ]; bool bToggleUserCommand = ( rCommand == toggleUserCommand ); bool bSelectUserCommand = ( !bToggleUserCommand && rCommand == selectUserCommand ); bool bToggleSystemCommand = ( !( bToggleUserCommand | bSelectUserCommand ) && rCommand == toggleSystemCommand ); bool bSelectSystemCommand = ( !( bToggleUserCommand | bSelectUserCommand | bToggleSystemCommand ) && rCommand == selectSystemCommand ); if( !( bToggleUserCommand | bSelectUserCommand | bToggleSystemCommand | bSelectSystemCommand ) ) { return; } /// Make sure the option name (first parameter after the command name) is valid. String convertedString; HELIUM_VERIFY( ( StringConverter< char, tchar_t >::Convert( convertedString, splitLine[ 1 ] ) ) ); Name optionName( convertedString ); if( optionName.IsEmpty() ) { HELIUM_TRACE( TraceLevels::Error, TXT( "ShaderResourceHandler: Skipping empty option in shader resource \"%s\".\n" ), *shaderPath.ToString() ); return; } // Make sure an existing toggle or selection option exists with the parsed option name. Shader::Options& rSystemOptions = rResourceData.GetSystemOptions(); Shader::Options& rUserOptions = rResourceData.GetUserOptions(); DynamicArray< Shader::Toggle >& rSystemToggles = rSystemOptions.GetToggles(); DynamicArray< Shader::Select >& rSystemSelects = rSystemOptions.GetSelects(); DynamicArray< Shader::Toggle >& rUserToggles = rUserOptions.GetToggles(); DynamicArray< Shader::Select >& rUserSelects = rUserOptions.GetSelects(); if( ParseLineDuplicateOptionCheck( optionName, rSystemToggles ) || ParseLineDuplicateOptionCheck( optionName, rSystemSelects ) || ParseLineDuplicateOptionCheck( optionName, rUserToggles ) || ParseLineDuplicateOptionCheck( optionName, rUserSelects ) ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "ShaderResourceHandler: Duplicate option name \"%s\" found in shader resource \"%s\". Only " ) TXT( "the first option will be used.\n" ) ), *optionName, *shaderPath.ToString() ); return; } // Handle shader-specific command flags (option applies to all shader types if no flags are specified). uint32_t shaderFlags = ( 1 << RShader::TYPE_MAX ) - 1; if( commandSplitCount > 1 ) { shaderFlags = 0; const CharString& rShaderFlags = splitCommand[ 1 ]; size_t shaderFlagCount = rShaderFlags.GetSize(); for( size_t flagIndex = 0; flagIndex < shaderFlagCount; ++flagIndex ) { char flagCharacter = rShaderFlags[ flagIndex ]; if( flagCharacter == 'v' ) { shaderFlags |= ( 1 << RShader::TYPE_VERTEX ); } else if( flagCharacter == 'p' ) { shaderFlags |= ( 1 << RShader::TYPE_PIXEL ); } } } // Parse the command parameters. if( bToggleUserCommand | bToggleSystemCommand ) { Shader::Toggle* pToggle = ( bToggleUserCommand ? rUserToggles : rSystemToggles ).New(); HELIUM_ASSERT( pToggle ); pToggle->name = optionName; pToggle->shaderTypeFlags = shaderFlags; if( splitCount > 2 ) { HELIUM_TRACE( TraceLevels::Warning, ( TXT( "ShaderResourceHandler: Extra tokens for toggle command \"%s\" in shader resource \"%s\" " ) TXT( "ignored.\n" ) ), *splitLine[ 1 ], *shaderPath.ToString() ); } } else { if( splitCount < 3 || ( splitCount < 4 && splitLine[ 2 ] == "NONE" ) ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "ShaderResourceHandler: Missing options for select command \"%s\" in shader resource " ) TXT( "\"%s\".\n" ) ), *splitLine[ 1 ], *shaderPath.ToString() ); return; } Shader::Select* pSelect = ( bSelectUserCommand ? rUserSelects : rSystemSelects ).New(); HELIUM_ASSERT( pSelect ); pSelect->name = optionName; pSelect->shaderTypeFlags = shaderFlags; pSelect->bOptional = ( splitLine[ 2 ] == "NONE" ); size_t choiceIndex = ( pSelect->bOptional ? 3 : 2 ); for( ; choiceIndex < splitCount; ++choiceIndex ) { HELIUM_VERIFY( ( StringConverter< char, tchar_t >::Convert( convertedString, splitLine[ choiceIndex ] ) ) ); pSelect->choices.New( convertedString ); } } }
/// Update property preloading for the given object load request. /// /// @param[in] pRequest Load request to update. /// /// @return True if preloading still needs processing, false if it is complete. bool GameObjectLoader::TickPreload( LoadRequest* pRequest ) { HELIUM_ASSERT( pRequest ); HELIUM_ASSERT( !( pRequest->stateFlags & ( LOAD_FLAG_LINKED | LOAD_FLAG_PRECACHED | LOAD_FLAG_LOADED ) ) ); PackageLoader* pPackageLoader = pRequest->pPackageLoader; HELIUM_ASSERT( pPackageLoader ); if( IsInvalid( pRequest->packageLoadRequestId ) ) { if( !pPackageLoader->TryFinishPreload() ) { // Still waiting for package loader preload. return false; } // Add an object load request. GameObjectPath path = pRequest->path; pRequest->packageLoadRequestId = pPackageLoader->BeginLoadObject( path ); if( IsInvalid( pRequest->packageLoadRequestId ) ) { pRequest->spObject = GameObject::FindObject( path ); GameObject* pObject = pRequest->spObject; if( pObject ) { HELIUM_TRACE( TRACE_WARNING, TXT( "GameObjectLoader: GameObject \"%s\" is not serialized, but was found in memory.\n" ), *path.ToString() ); // Make sure the object is preloaded and linked, but still perform resource caching and load // finalization if necessary. pObject->SetFlags( GameObject::FLAG_PRELOADED | GameObject::FLAG_LINKED ); AtomicOrRelease( pRequest->stateFlags, LOAD_FLAG_PRELOADED | LOAD_FLAG_LINKED ); return true; } HELIUM_TRACE( TRACE_ERROR, TXT( "GameObjectLoader: GameObject \"%s\" is not serialized and does not exist in memory.\n" ), *path.ToString() ); AtomicOrRelease( pRequest->stateFlags, LOAD_FLAG_FULLY_LOADED | LOAD_FLAG_ERROR ); return true; } } HELIUM_ASSERT( IsValid( pRequest->packageLoadRequestId ) ); bool bFinished = pPackageLoader->TryFinishLoadObject( pRequest->packageLoadRequestId, pRequest->spObject, pRequest->linkTable ); if( !bFinished ) { // Still waiting for object to load. return false; } // Preload complete. SetInvalid( pRequest->packageLoadRequestId ); AtomicOrRelease( pRequest->stateFlags, LOAD_FLAG_PRELOADED ); return true; }
/// @copydoc PackageLoader::BeginLoadObject() size_t CachePackageLoader::BeginLoadObject( GameObjectPath path ) { HELIUM_ASSERT( m_pCache ); // Don't load packages from the cache, but instead create them dynamically. if( path.IsPackage() ) { HELIUM_TRACE( TraceLevels::Debug, TXT( "CachePackageLoader::BeginLoadObject(): \"%s\" is a package, resolving immediately.\n" ), *path.ToString() ); LoadRequest* pRequest = m_loadRequestPool.Allocate(); HELIUM_ASSERT( pRequest ); pRequest->pEntry = NULL; ResolvePackage( pRequest->spObject, path ); HELIUM_ASSERT( pRequest->spObject ); SetInvalid( pRequest->asyncLoadId ); pRequest->pAsyncLoadBuffer = NULL; pRequest->pSerializedData = NULL; pRequest->pPropertyStreamEnd = NULL; pRequest->pPersistentResourceStreamEnd = NULL; HELIUM_ASSERT( pRequest->typeLinkTable.IsEmpty() ); HELIUM_ASSERT( pRequest->objectLinkTable.IsEmpty() ); HELIUM_ASSERT( !pRequest->spType ); HELIUM_ASSERT( !pRequest->spTemplate ); HELIUM_ASSERT( !pRequest->spOwner ); SetInvalid( pRequest->templateLinkIndex ); SetInvalid( pRequest->ownerLinkIndex ); pRequest->flags = LOAD_FLAG_PRELOADED; size_t requestId = m_loadRequests.Add( pRequest ); return requestId; } const Cache::Entry* pEntry = m_pCache->FindEntry( path, 0 ); if( !pEntry ) { HELIUM_TRACE( TraceLevels::Debug, ( TXT( "CachePackageLoader::BeginLoadObject(): \"%s\" is not cached in this package. No load " ) TXT( "request added.\n" ) ), *path.ToString() ); return Invalid< size_t >(); } #ifndef NDEBUG size_t loadRequestSize = m_loadRequests.GetSize(); for( size_t loadRequestIndex = 0; loadRequestIndex < loadRequestSize; ++loadRequestIndex ) { if( !m_loadRequests.IsElementValid( loadRequestIndex ) ) { continue; } LoadRequest* pRequest = m_loadRequests[ loadRequestIndex ]; HELIUM_ASSERT( pRequest ); HELIUM_ASSERT( pRequest->pEntry != pEntry ); if( pRequest->pEntry == pEntry ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "CachePackageLoader::BeginLoadObject(): Duplicate load request of \"%s\". No load " ) TXT( "request added.\n" ) ), *path.ToString() ); return Invalid< size_t >(); } } #endif LoadRequest* pRequest = m_loadRequestPool.Allocate(); HELIUM_ASSERT( pRequest ); pRequest->pEntry = pEntry; HELIUM_ASSERT( !pRequest->spObject ); SetInvalid( pRequest->asyncLoadId ); pRequest->pAsyncLoadBuffer = NULL; pRequest->pSerializedData = NULL; pRequest->pPropertyStreamEnd = NULL; pRequest->pPersistentResourceStreamEnd = NULL; HELIUM_ASSERT( pRequest->typeLinkTable.IsEmpty() ); HELIUM_ASSERT( pRequest->objectLinkTable.IsEmpty() ); HELIUM_ASSERT( !pRequest->spType ); HELIUM_ASSERT( !pRequest->spTemplate ); HELIUM_ASSERT( !pRequest->spOwner ); SetInvalid( pRequest->templateLinkIndex ); SetInvalid( pRequest->ownerLinkIndex ); pRequest->flags = 0; // If a fully-loaded object already exists with the same name, do not attempt to re-load the object (just mark // the request as complete). pRequest->spObject = GameObject::FindObject( pEntry->path ); GameObject* pObject = pRequest->spObject; if( pObject && pObject->IsFullyLoaded() ) { HELIUM_TRACE( TraceLevels::Debug, ( TXT( "CachePackageLoader::BeginLoadObject(): \"%s\" is already fully loaded. Bypassing load " ) TXT( "process.\n" ) ), *path.ToString() ); pRequest->flags = LOAD_FLAG_PRELOADED; } else { HELIUM_ASSERT( !pObject || !pObject->GetAnyFlagSet( GameObject::FLAG_LOADED | GameObject::FLAG_LINKED ) ); HELIUM_TRACE( TraceLevels::Debug, TXT( "CachePackageLoader::BeginLoadObject(): Issuing async load of property data for \"%s\".\n" ), *path.ToString() ); size_t entrySize = pEntry->size; pRequest->pAsyncLoadBuffer = static_cast< uint8_t* >( DefaultAllocator().Allocate( entrySize ) ); HELIUM_ASSERT( pRequest->pAsyncLoadBuffer ); AsyncLoader& rLoader = AsyncLoader::GetStaticInstance(); pRequest->asyncLoadId = rLoader.QueueRequest( pRequest->pAsyncLoadBuffer, m_pCache->GetCacheFileName(), pEntry->offset, entrySize ); HELIUM_ASSERT( IsValid( pRequest->asyncLoadId ) ); } size_t requestId = m_loadRequests.Add( pRequest ); HELIUM_TRACE( TraceLevels::Debug, ( TXT( "CachePackageLoader::BeginLoadObject(): Load request for \"%s\" added (ID: %" ) TPRIuSZ TXT( ").\n" ) ), *path.ToString(), requestId ); return requestId; }
/// Add or update an entry in the cache. /// /// @param[in] path GameObject path. /// @param[in] subDataIndex Sub-data index associated with the cached data. /// @param[in] pData Data to cache. /// @param[in] timestamp Timestamp value to associate with the entry in the cache. /// @param[in] size Number of bytes to cache. /// /// @return True if the cache was updated successfully, false if not. bool Cache::CacheEntry( GameObjectPath path, uint32_t subDataIndex, const void* pData, int64_t timestamp, uint32_t size ) { HELIUM_ASSERT( pData || size == 0 ); Status status; status.Read( m_cacheFileName.GetData() ); int64_t cacheFileSize = status.m_Size; uint64_t entryOffset = ( cacheFileSize == -1 ? 0 : static_cast< uint64_t >( cacheFileSize ) ); HELIUM_ASSERT( m_pEntryPool ); Entry* pEntryUpdate = m_pEntryPool->Allocate(); HELIUM_ASSERT( pEntryUpdate ); pEntryUpdate->offset = entryOffset; pEntryUpdate->timestamp = timestamp; pEntryUpdate->path = path; pEntryUpdate->subDataIndex = subDataIndex; pEntryUpdate->size = size; uint64_t originalOffset = 0; int64_t originalTimestamp = 0; uint32_t originalSize = 0; EntryKey key; key.path = path; key.subDataIndex = subDataIndex; EntryMapType::Accessor entryAccessor; bool bNewEntry = m_entryMap.Insert( entryAccessor, KeyValue< EntryKey, Entry* >( key, pEntryUpdate ) ); if( bNewEntry ) { HELIUM_TRACE( TraceLevels::Info, TXT( "Cache: Adding \"%s\" to cache \"%s\".\n" ), *path.ToString(), *m_cacheFileName ); m_entries.Push( pEntryUpdate ); } else { HELIUM_TRACE( TraceLevels::Info, TXT( "Cache: Updating \"%s\" in cache \"%s\".\n" ), *path.ToString(), *m_cacheFileName ); m_pEntryPool->Release( pEntryUpdate ); pEntryUpdate = entryAccessor->Second(); HELIUM_ASSERT( pEntryUpdate ); originalOffset = pEntryUpdate->offset; originalTimestamp = pEntryUpdate->timestamp; originalSize = pEntryUpdate->size; if( originalSize < size ) { pEntryUpdate->offset = entryOffset; } else { entryOffset = originalOffset; } pEntryUpdate->timestamp = timestamp; pEntryUpdate->size = size; } AsyncLoader& rLoader = AsyncLoader::GetStaticInstance(); rLoader.Lock(); bool bCacheSuccess = true; FileStream* pCacheStream = FileStream::OpenFileStream( m_cacheFileName, FileStream::MODE_WRITE, false ); if( !pCacheStream ) { HELIUM_TRACE( TraceLevels::Error, TXT( "Cache: Failed to open cache \"%s\" for writing.\n" ), *m_cacheFileName ); bCacheSuccess = false; } else { HELIUM_TRACE( TraceLevels::Info, TXT( "Cache: Caching \"%s\" to \"%s\" (%" ) TPRIu32 TXT( " bytes @ offset %" ) TPRIu64 TXT( ").\n" ), *path.ToString(), *m_cacheFileName, size, entryOffset ); uint64_t seekOffset = static_cast< uint64_t >( pCacheStream->Seek( static_cast< int64_t >( entryOffset ), SeekOrigins::SEEK_ORIGIN_BEGIN ) ); if( seekOffset != entryOffset ) { HELIUM_TRACE( TraceLevels::Error, TXT( "Cache: Cache file offset seek failed.\n" ) ); if( bNewEntry ) { m_entries.Pop(); m_entryMap.Remove( entryAccessor ); m_pEntryPool->Release( pEntryUpdate ); } else { pEntryUpdate->offset = originalOffset; pEntryUpdate->timestamp = originalTimestamp; pEntryUpdate->size = originalSize; } bCacheSuccess = false; } else { size_t writeSize = pCacheStream->Write( pData, 1, size ); if( writeSize != size ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "Cache: Failed to write %" ) TPRIu32 TXT( " bytes to cache \"%s\" (%" ) TPRIuSZ TXT( " bytes written).\n" ) ), size, *m_cacheFileName, writeSize ); if( bNewEntry ) { m_entries.Pop(); m_entryMap.Remove( entryAccessor ); m_pEntryPool->Release( pEntryUpdate ); } else { pEntryUpdate->offset = originalOffset; pEntryUpdate->timestamp = originalTimestamp; pEntryUpdate->size = originalSize; } bCacheSuccess = false; } else { HELIUM_TRACE( TraceLevels::Info, TXT( "Cache: Rewriting TOC file \"%s\".\n" ), *m_tocFileName ); FileStream* pTocStream = FileStream::OpenFileStream( m_tocFileName, FileStream::MODE_WRITE, true ); if( !pTocStream ) { HELIUM_TRACE( TraceLevels::Error, TXT( "Cache: Failed to open TOC \"%s\" for writing.\n" ), *m_tocFileName ); } else { BufferedStream* pBufferedStream = new BufferedStream( pTocStream ); HELIUM_ASSERT( pBufferedStream ); pBufferedStream->Write( &TOC_MAGIC, sizeof( TOC_MAGIC ), 1 ); pBufferedStream->Write( &sm_Version, sizeof( sm_Version ), 1 ); uint32_t entryCount = static_cast< uint32_t >( m_entries.GetSize() ); pBufferedStream->Write( &entryCount, sizeof( entryCount ), 1 ); String entryPath; uint_fast32_t entryCountFast = entryCount; for( uint_fast32_t entryIndex = 0; entryIndex < entryCountFast; ++entryIndex ) { Entry* pEntry = m_entries[ entryIndex ]; HELIUM_ASSERT( pEntry ); pEntry->path.ToString( entryPath ); HELIUM_ASSERT( entryPath.GetSize() < UINT16_MAX ); uint16_t pathSize = static_cast< uint16_t >( entryPath.GetSize() ); pBufferedStream->Write( &pathSize, sizeof( pathSize ), 1 ); pBufferedStream->Write( *entryPath, sizeof( tchar_t ), pathSize ); pBufferedStream->Write( &pEntry->subDataIndex, sizeof( pEntry->subDataIndex ), 1 ); pBufferedStream->Write( &pEntry->offset, sizeof( pEntry->offset ), 1 ); pBufferedStream->Write( &pEntry->timestamp, sizeof( pEntry->timestamp ), 1 ); pBufferedStream->Write( &pEntry->size, sizeof( pEntry->size ), 1 ); } delete pBufferedStream; delete pTocStream; } } } delete pCacheStream; } rLoader.Unlock(); return bCacheSuccess; }
/// @copydoc GameObjectLoader::CacheObject() bool EditorObjectLoader::CacheObject( GameObject* pObject, bool bEvictPlatformPreprocessedResourceData ) { HELIUM_ASSERT( pObject ); // Don't cache broken objects or packages. if( pObject->GetAnyFlagSet( GameObject::FLAG_BROKEN ) || pObject->IsPackage() ) { return false; } // Make sure we have an object preprocessor instance with which to cache the object. ObjectPreprocessor* pObjectPreprocessor = ObjectPreprocessor::GetStaticInstance(); if( !pObjectPreprocessor ) { HELIUM_TRACE( TRACE_WARNING, TXT( "EditorObjectLoader::CacheObject(): Missing ObjectPreprocessor to use for caching.\n" ) ); return false; } // Configuration objects should not be cached. GameObjectPath objectPath = pObject->GetPath(); Config& rConfig = Config::GetStaticInstance(); GameObjectPath configPackagePath = rConfig.GetConfigContainerPackagePath(); HELIUM_ASSERT( !configPackagePath.IsEmpty() ); for( GameObjectPath testPath = objectPath; !testPath.IsEmpty(); testPath = testPath.GetParent() ) { if( testPath == configPackagePath ) { return false; } } // Get the timestamp for the object based on the timestamp of its source package file and, if it's a resource, // the timestamp of the source resource file. GameObject* pPackageObject; for( pPackageObject = pObject; pPackageObject && !pPackageObject->IsPackage(); pPackageObject = pPackageObject->GetOwner() ) { } HELIUM_ASSERT( pPackageObject ); PackageLoader* pPackageLoader = Reflect::AssertCast< Package >( pPackageObject )->GetLoader(); HELIUM_ASSERT( pPackageLoader ); HELIUM_ASSERT( pPackageLoader->IsSourcePackageFile() ); int64_t objectTimestamp = pPackageLoader->GetFileTimestamp(); if( !pObject->IsDefaultTemplate() ) { Resource* pResource = Reflect::SafeCast< Resource >( pObject ); if( pResource ) { GameObjectPath baseResourcePath = pResource->GetPath(); HELIUM_ASSERT( !baseResourcePath.IsPackage() ); for( ; ; ) { GameObjectPath parentPath = baseResourcePath.GetParent(); if( parentPath.IsEmpty() || parentPath.IsPackage() ) { break; } baseResourcePath = parentPath; } Path sourceFilePath; if ( !File::GetDataDirectory( sourceFilePath ) ) { HELIUM_TRACE( TRACE_WARNING, TXT( "EditorObjectLoader::CacheObject(): Could not obtain data directory.\n" ) ); return false; } sourceFilePath += baseResourcePath.ToFilePathString().GetData(); int64_t sourceFileTimestamp = sourceFilePath.ModifiedTime(); if( sourceFileTimestamp > objectTimestamp ) { objectTimestamp = sourceFileTimestamp; } } } // Cache the object. bool bSuccess = pObjectPreprocessor->CacheObject( pObject, objectTimestamp, bEvictPlatformPreprocessedResourceData ); if( !bSuccess ) { HELIUM_TRACE( TRACE_ERROR, TXT( "EditorObjectLoader: Failed to cache object \"%s\".\n" ), *objectPath.ToString() ); } return bSuccess; }