/// Recursive function for resolving a package request. /// /// @param[out] rspPackage Resolved package. /// @param[in] packagePath Package object path. void CachePackageLoader::ResolvePackage( AssetPtr& rspPackage, AssetPath packagePath ) { HELIUM_ASSERT( !packagePath.IsEmpty() ); rspPackage = Asset::FindObject( packagePath ); if( !rspPackage ) { AssetPtr spParent; AssetPath parentPath = packagePath.GetParent(); if( !parentPath.IsEmpty() ) { ResolvePackage( spParent, parentPath ); HELIUM_ASSERT( spParent ); } HELIUM_VERIFY( Asset::CreateObject( rspPackage, Package::GetStaticType(), packagePath.GetName(), spParent ) ); HELIUM_ASSERT( rspPackage ); HELIUM_ASSERT( rspPackage->IsA( Package::GetStaticType()->GetMetaClass() ) ); } rspPackage->SetFlags( Asset::FLAG_PRELOADED | Asset::FLAG_LINKED | Asset::FLAG_LOADED ); }
/// Find an object based on its path name. /// /// @param[in] path FilePath of the object to locate. /// /// @return Pointer to the object if found, null pointer if not found. Asset* Asset::FindObject( AssetPath path ) { // Make sure the path isn't empty. if( path.IsEmpty() ) { return NULL; } // Assemble a list of object names and instance indices, from the top level on down. size_t pathDepth = 0; size_t packageDepth = 0; for( AssetPath testPath = path; !testPath.IsEmpty(); testPath = testPath.GetParent() ) { ++pathDepth; if( testPath.IsPackage() ) { ++packageDepth; } } StackMemoryHeap<>& rStackHeap = ThreadLocalStackAllocator::GetMemoryHeap(); StackMemoryHeap<>::Marker stackMarker( rStackHeap ); Name* pPathNames = static_cast< Name* >( rStackHeap.Allocate( sizeof( Name ) * pathDepth ) ); HELIUM_ASSERT( pPathNames ); uint32_t* pInstanceIndices = static_cast< uint32_t* >( rStackHeap.Allocate( sizeof( uint32_t ) * pathDepth ) ); HELIUM_ASSERT( pInstanceIndices ); size_t pathIndex = pathDepth; for( AssetPath testPath = path; !testPath.IsEmpty(); testPath = testPath.GetParent() ) { HELIUM_ASSERT( pathIndex != 0 ); --pathIndex; pPathNames[ pathIndex ] = testPath.GetName(); pInstanceIndices[ pathIndex ] = testPath.GetInstanceIndex(); } HELIUM_ASSERT( pathIndex == 0 ); // Search from the root. return FindChildOf( NULL, pPathNames, pInstanceIndices, pathDepth, packageDepth ); }
/// Get the path to the package containing all world instances. /// /// @return World package path. AssetPath WorldManager::GetRootSceneDefinitionPackagePath() const { static AssetPath worldPackagePath; if( worldPackagePath.IsEmpty() ) { HELIUM_VERIFY( worldPackagePath.Set( TXT( "/Worlds" ) ) ); } return worldPackagePath; }
/// Initialize this manager. /// /// @return True if this manager was initialized successfully, false if not. /// /// @see Shutdown() bool WorldManager::Initialize() { HELIUM_ASSERT( !m_spRootSceneDefinitionsPackage ); // 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. AssetPath rootSceneDefinitionsPackagePath = GetRootSceneDefinitionPackagePath(); HELIUM_ASSERT( !rootSceneDefinitionsPackagePath.IsEmpty() ); HELIUM_ASSERT( rootSceneDefinitionsPackagePath.GetParent().IsEmpty() ); bool bCreateResult = Asset::Create< Package >( m_spRootSceneDefinitionsPackage, rootSceneDefinitionsPackagePath.GetName(), NULL ); HELIUM_ASSERT( bCreateResult ); if( !bCreateResult ) { HELIUM_TRACE( TraceLevels::Error, TXT( "WorldManager::Initialize(): Failed to create world definition package \"%s\".\n" ), *rootSceneDefinitionsPackagePath.ToString() ); return false; } HELIUM_ASSERT( m_spRootSceneDefinitionsPackage ); // 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; }
/// Initialize this package loader. /// /// @param[in] packagePath Asset path of the package to load. /// /// @return True if this loader was initialized successfully, false if not. /// /// @see Shutdown() bool LoosePackageLoader::Initialize( AssetPath packagePath ) { Shutdown(); // Make sure the path represents a package. if( packagePath.IsEmpty() ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader::Initialize(): Empty package path specified.\n" ) ); return false; } HELIUM_TRACE( TraceLevels::Debug, TXT( "LoosePackageLoader::Initialize(): Initializing loader for package \"%s\".\n" ), *packagePath.ToString() ); if( !packagePath.IsPackage() ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader::Initialize(): \"%s\" does not represent a package path.\n" ), *packagePath.ToString() ); return false; } // Store the package path. m_packagePath = packagePath; // Attempt to locate the specified package if it already happens to exist. m_spPackage = Asset::Find< Package >( packagePath ); Package* pPackage = m_spPackage; if( pPackage ) { if( pPackage->GetLoader() ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader::Initialize(): Package \"%s\" already has a loader.\n" ), *packagePath.ToString() ); m_spPackage.Release(); return false; } pPackage->SetLoader( this ); } else { // Make sure we don't have a name clash with a non-package object. AssetPtr spObject( Asset::FindObject( packagePath ) ); if( spObject ) { HELIUM_ASSERT( !spObject->IsPackage() ); HELIUM_TRACE( TraceLevels::Error, ( TXT( "PackageLoader::Initialize(): Package loader cannot be initialized for \"%s\", as an " ) TXT( "object with the same name exists that is not a package.\n" ) ), *packagePath.ToString() ); return false; } } // Build the package file path. If the package is a user configuration package, use the user data directory, // otherwise use the global data directory. Config& rConfig = Config::GetStaticInstance(); FilePath dataDirectory; if ( !FileLocations::GetDataDirectory( dataDirectory ) ) { HELIUM_TRACE( TraceLevels::Error, TXT( "PackageLoader::Initialize(): Could not obtain user data directory." ) ); return false; } // Set up to read the TOC (which may not exist) //SetInvalid( m_packageTocFileSize ); // First do this check without a trailing "/" so that FilePath has to actually look at the file system FilePath package_dir = dataDirectory + packagePath.ToFilePathString().GetData(); if (!package_dir.Exists()) { // Some packages like types or uninitialized user config packages may not exist on file system m_packageDirPath = package_dir + TXT("/"); return true; } if (!package_dir.IsDirectory()) { // Packages should not be files return false; } // But internally we will store this m_packageDirPath = package_dir + TXT("/"); return true; }
/// @copydoc PackageLoader::BeginLoadObject() size_t LoosePackageLoader::BeginLoadObject( AssetPath path, Reflect::ObjectResolver *pResolver, bool forceReload ) { HELIUM_TRACE( TraceLevels::Info, TXT(" LoosePackageLoader::BeginLoadObject - Loading path %s\n"), *path.ToString() ); HELIUM_TRACE( TraceLevels::Debug, TXT( "LoosePackageLoader::BeginLoadObject: Beginning load for path \"%s\".\n"), *path.ToString()); HELIUM_TRACE( TraceLevels::Debug, TXT( "LoosePackageLoader::BeginLoadObject: Beginning load for path \"%s\". pResolver = %x\n"), *path.ToString(), pResolver); // Make sure preloading has completed. HELIUM_ASSERT( m_preloadedCounter != 0 ); if( !m_preloadedCounter ) { return Invalid< size_t >(); } // If this package is requested, simply provide the (already loaded) package instance. if( path == m_packagePath ) { LoadRequest* pRequest = m_loadRequestPool.Allocate(); HELIUM_ASSERT( pRequest ); HELIUM_ASSERT( m_spPackage ); pRequest->spObject = m_spPackage.Ptr(); SetInvalid( pRequest->index ); HELIUM_ASSERT( !pRequest->spType ); HELIUM_ASSERT( !pRequest->spTemplate ); HELIUM_ASSERT( !pRequest->spOwner ); SetInvalid( pRequest->templateLoadId ); SetInvalid( pRequest->ownerLoadId ); SetInvalid( pRequest->persistentResourceDataLoadId ); pRequest->pCachedObjectDataBuffer = NULL; pRequest->cachedObjectDataBufferSize = 0; SetInvalid( pRequest->asyncFileLoadId ); pRequest->pAsyncFileLoadBuffer = NULL; pRequest->asyncFileLoadBufferSize = 0; pRequest->pResolver = NULL; pRequest->forceReload = forceReload; pRequest->flags = LOAD_FLAG_PRELOADED; size_t requestId = m_loadRequests.Add( pRequest ); return requestId; } size_t objectIndex = FindObjectByPath( path ); size_t objectCount = GetObjectCount(); if( objectIndex >= objectCount ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader::BeginLoadObject(): Failed to locate \"%s\" for loading. Verify the file exists.\n" ), *path.ToString() ); return Invalid< size_t >(); } SerializedObjectData& rObjectData = m_objects[ objectIndex ]; // Verify that the metadata was read successfully if( !rObjectData.bMetadataGood ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader::BeginLoadObject(): Failed to read metadata for object \"%s\" during PackagePreload. Search log for parsing errors.\n" ), *path.ToString() ); return Invalid< size_t >(); } // Locate the type object. HELIUM_ASSERT( !rObjectData.typeName.IsEmpty() ); AssetType* pType = AssetType::Find( rObjectData.typeName ); if( !pType ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader::BeginLoadObject(): Failed to locate type \"%s\" for loading object \"%s\".\n" ), *rObjectData.typeName, *path.ToString() ); HELIUM_TRACE( TraceLevels::Info, TXT( "Current registered types:\n" ) ); for ( AssetType::ConstIterator iter = AssetType::GetTypeBegin(); iter != AssetType::GetTypeEnd(); ++iter) { HELIUM_TRACE( TraceLevels::Info, TXT( " - %s\n" ), *iter->GetName() ); } 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->index != objectIndex ); if( pRequest->index == objectIndex ) { return Invalid< size_t >(); } } #endif LoadRequest* pRequest = m_loadRequestPool.Allocate(); HELIUM_ASSERT( pRequest ); HELIUM_ASSERT( !pRequest->spObject ); pRequest->index = objectIndex; pRequest->spType = pType; HELIUM_ASSERT( !pRequest->spTemplate ); HELIUM_ASSERT( !pRequest->spOwner ); SetInvalid( pRequest->templateLoadId ); SetInvalid( pRequest->ownerLoadId ); SetInvalid( pRequest->persistentResourceDataLoadId ); pRequest->pCachedObjectDataBuffer = NULL; pRequest->cachedObjectDataBufferSize = 0; SetInvalid( pRequest->asyncFileLoadId ); pRequest->pAsyncFileLoadBuffer = NULL; pRequest->asyncFileLoadBufferSize = 0; pRequest->pResolver = pResolver; pRequest->forceReload = forceReload; 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). if ( !forceReload ) { pRequest->spObject = Asset::FindObject( path ); } Asset* pObject = pRequest->spObject; if( pObject && pObject->IsFullyLoaded() ) { pRequest->flags = LOAD_FLAG_PRELOADED; } else { HELIUM_ASSERT( !pObject || !pObject->GetAnyFlagSet( Asset::FLAG_LOADED | Asset::FLAG_LINKED ) ); // Begin loading the template and owner objects. Note that there isn't much reason to check for failure // until we tick this request, as we need to make sure any other load requests for the template/owner that // did succeed are properly synced anyway. AssetLoader* pAssetLoader = AssetLoader::GetStaticInstance(); HELIUM_ASSERT( pAssetLoader ); if( rObjectData.templatePath.IsEmpty() ) { // Make sure the template is fully loaded. Asset* pTemplate = pType->GetTemplate(); rObjectData.templatePath = pTemplate->GetPath(); if( pTemplate->IsFullyLoaded() ) { pRequest->spTemplate = pTemplate; } else { pRequest->templateLoadId = pAssetLoader->BeginLoadObject( rObjectData.templatePath ); } } else { pRequest->templateLoadId = pAssetLoader->BeginLoadObject( rObjectData.templatePath ); } AssetPath ownerPath = path.GetParent(); if( ownerPath == m_packagePath ) { // Easy check: if the owner is this package (which is likely), we don't need to load it. pRequest->spOwner = m_spPackage.Ptr(); } else if( !ownerPath.IsEmpty() ) { pRequest->ownerLoadId = pAssetLoader->BeginLoadObject( ownerPath ); } } size_t requestId = m_loadRequests.Add( pRequest ); return requestId; }
/// Begin asynchronous pre-loading of package information. /// /// @see TryFinishPreload() bool LoosePackageLoader::BeginPreload() { HELIUM_ASSERT( !m_startPreloadCounter ); HELIUM_ASSERT( !m_preloadedCounter ); HELIUM_ASSERT( IsInvalid( m_parentPackageLoadId ) ); // Load the parent package if we need to create the current package. if( !m_spPackage ) { AssetPath parentPackagePath = m_packagePath.GetParent(); if( !parentPackagePath.IsEmpty() ) { AssetLoader* pAssetLoader = AssetLoader::GetStaticInstance(); HELIUM_ASSERT( pAssetLoader ); m_parentPackageLoadId = pAssetLoader->BeginLoadObject( parentPackagePath ); HELIUM_ASSERT( IsValid( m_parentPackageLoadId ) ); } } AsyncLoader &rAsyncLoader = AsyncLoader::GetStaticInstance(); if ( !m_packageDirPath.Exists() ) { HELIUM_TRACE( TraceLevels::Warning, "LoosePackageLoader::BeginPreload - Package physical path '%s' does not exist\n", m_packageDirPath.c_str()); } else if ( !m_packageDirPath.IsDirectory() ) { HELIUM_TRACE( TraceLevels::Warning, "LoosePackageLoader::BeginPreload - Package physical path '%s' is not a directory\n", m_packageDirPath.c_str()); } else { DirectoryIterator packageDirectory( m_packageDirPath ); HELIUM_TRACE( TraceLevels::Info, TXT(" LoosePackageLoader::BeginPreload - Issuing read requests for all files in %s\n"), m_packageDirPath.c_str() ); for( ; !packageDirectory.IsDone(); packageDirectory.Next() ) { const DirectoryIteratorItem& item = packageDirectory.GetItem(); #if HELIUM_TOOLS if ( item.m_Path.IsDirectory() ) { AssetPath packagePath; std::string name = item.m_Path.DirectoryAsVector().back(); packagePath.Set( Name( name.c_str() ), true, m_packagePath ); m_childPackagePaths.Add( packagePath ); HELIUM_TRACE( TraceLevels::Info, TXT("- Skipping directory [%s]\n"), item.m_Path.c_str(), item.m_Path.Extension().c_str() ); } else #endif if ( item.m_Path.Extension() == Persist::ArchiveExtensions[ Persist::ArchiveTypes::Json ] ) { HELIUM_TRACE( TraceLevels::Info, TXT("- Reading file [%s]\n"), item.m_Path.c_str() ); FileReadRequest *request = m_fileReadRequests.New(); request->expectedSize = item.m_Size; HELIUM_ASSERT( item.m_Size < UINT32_MAX ); // Create a buffer for the file to be read into temporarily request->pLoadBuffer = DefaultAllocator().Allocate( static_cast< size_t > ( item.m_Size ) + 1 ); static_cast< char* >( request->pLoadBuffer )[ static_cast< size_t > ( item.m_Size ) ] = '\0'; // for efficiency parsing text files HELIUM_ASSERT( request->pLoadBuffer ); // Queue up the read request->asyncLoadId = rAsyncLoader.QueueRequest( request->pLoadBuffer, String( item.m_Path.c_str() ), 0, static_cast< size_t >( item.m_Size ) ); HELIUM_ASSERT( IsValid( request->asyncLoadId ) ); request->filePath = item.m_Path; request->fileTimestamp = item.m_ModTime; } else { HELIUM_TRACE( TraceLevels::Info, TXT("- Skipping file [%s] (Extension is %s)\n"), item.m_Path.c_str(), item.m_Path.Extension().c_str() ); } } } AtomicExchangeRelease( m_startPreloadCounter, 1 ); return true; }
/// Update processing of object property preloading for a given load request. /// /// @param[in] pRequest Load request to process. /// /// @return True if object property preloading for the given load request has completed, false if not. bool LoosePackageLoader::TickDeserialize( LoadRequest* pRequest ) { HELIUM_ASSERT( pRequest ); HELIUM_ASSERT( !( pRequest->flags & LOAD_FLAG_PROPERTY_PRELOADED ) ); Asset* pObject = pRequest->spObject; HELIUM_ASSERT( pRequest->index < m_objects.GetSize() ); SerializedObjectData& rObjectData = m_objects[ pRequest->index ]; // Wait for the template and owner objects to load. AssetLoader* pAssetLoader = AssetLoader::GetStaticInstance(); HELIUM_ASSERT( pAssetLoader ); if( !rObjectData.templatePath.IsEmpty() ) { if( IsValid( pRequest->templateLoadId ) ) { if( !pAssetLoader->TryFinishLoad( pRequest->templateLoadId, pRequest->spTemplate ) ) { return false; } SetInvalid( pRequest->templateLoadId ); } if( !pRequest->spTemplate ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader: Failed to load template object for \"%s\".\n" ), *rObjectData.objectPath.ToString() ); if( pObject ) { pObject->SetFlags( Asset::FLAG_PRELOADED | Asset::FLAG_LINKED ); pObject->ConditionalFinalizeLoad(); } pRequest->flags |= LOAD_FLAG_PRELOADED | LOAD_FLAG_ERROR; return true; } } HELIUM_ASSERT( IsInvalid( pRequest->templateLoadId ) ); Asset* pTemplate = pRequest->spTemplate; AssetPath ownerPath = rObjectData.objectPath.GetParent(); if( !ownerPath.IsEmpty() ) { if( IsValid( pRequest->ownerLoadId ) ) { if( !pAssetLoader->TryFinishLoad( pRequest->ownerLoadId, pRequest->spOwner ) ) { return false; } SetInvalid( pRequest->ownerLoadId ); } if( !pRequest->spOwner ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader: Failed to load owner object for \"%s\".\n" ), *rObjectData.objectPath.ToString() ); if( pObject ) { pObject->SetFlags( Asset::FLAG_PRELOADED | Asset::FLAG_LINKED ); pObject->ConditionalFinalizeLoad(); } pRequest->flags |= LOAD_FLAG_PRELOADED | LOAD_FLAG_ERROR; return true; } } HELIUM_ASSERT( IsInvalid( pRequest->ownerLoadId ) ); Asset* pOwner = pRequest->spOwner; AssetType* pType = pRequest->spType; HELIUM_ASSERT( pType ); HELIUM_ASSERT( !pOwner || pOwner->IsFullyLoaded() ); HELIUM_ASSERT( !pTemplate || pTemplate->IsFullyLoaded() ); AsyncLoader& rAsyncLoader = AsyncLoader::GetStaticInstance(); FilePath object_file_path = m_packageDirPath + *rObjectData.objectPath.GetName() + TXT( "." ) + Persist::ArchiveExtensions[ Persist::ArchiveTypes::Json ]; bool load_properties_from_file = true; size_t object_file_size = 0; if ( !IsValid( pRequest->asyncFileLoadId ) ) { if (!object_file_path.IsFile()) { if (pType->GetMetaClass()->IsType( Reflect::GetMetaClass< Resource >() )) { HELIUM_TRACE( TraceLevels::Info, TXT( "LoosePackageLoader::TickDeserialize(): No object file found for resource \"%s\". Expected file location: \"%s\". This is normal for newly added resources.\n" ), *rObjectData.objectPath.ToString(), *object_file_path); // We will allow continuing to load using all default properties. This behavior is to support dropping resources into the // data property and autogenerating objects from them. load_properties_from_file = false; } else { HELIUM_TRACE( TraceLevels::Warning, TXT( "LoosePackageLoader::TickDeserialize(): No object file found for object \"%s\". Expected file location: \"%s\"\n" ), *rObjectData.objectPath.ToString(), *object_file_path); } } else { Status status; status.Read( object_file_path.Get().c_str() ); int64_t i64_object_file_size = status.m_Size; if( i64_object_file_size == -1 ) { HELIUM_TRACE( TraceLevels::Warning, TXT( "LoosePackageLoader::TickDeserialize(): Could not get file size for object file of object \"%s\". Expected file location: \"%s\"\n" ), *rObjectData.objectPath.ToString(), *object_file_path ); } else if( i64_object_file_size == 0 ) { HELIUM_TRACE( TraceLevels::Warning, TXT( "LoosePackageLoader::TickDeserialize(): Object file \"%s\" for objct \"%s\" is empty.\n" ), *object_file_path, *rObjectData.objectPath.ToString() ); } else if( static_cast< uint64_t >( i64_object_file_size ) > static_cast< uint64_t >( ~static_cast< size_t >( 0 ) ) ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "LoosePackageLoader::TickDeserialize(): Object file \"%s\" exceeds the maximum size supported by " ) TXT( "the current platform (file size: %" ) PRIu64 TXT( " bytes; max supported: %" ) PRIuSZ TXT( " bytes).\n" ) ), object_file_path.c_str(), static_cast< uint64_t >( i64_object_file_size ), ~static_cast< size_t >( 0 ) ); } else { object_file_size = static_cast< size_t >(i64_object_file_size); } } if (!load_properties_from_file) { HELIUM_ASSERT(!object_file_size); } else if (!object_file_size) { pRequest->flags |= LOAD_FLAG_PRELOADED | LOAD_FLAG_ERROR; return true; } else { HELIUM_ASSERT( !pRequest->pAsyncFileLoadBuffer ); pRequest->pAsyncFileLoadBuffer = DefaultAllocator().Allocate( object_file_size ); HELIUM_ASSERT( pRequest->pAsyncFileLoadBuffer ); pRequest->asyncFileLoadBufferSize = object_file_size; pRequest->asyncFileLoadId = rAsyncLoader.QueueRequest( pRequest->pAsyncFileLoadBuffer, String(object_file_path.c_str()), 0, pRequest->asyncFileLoadBufferSize); } } size_t bytesRead = 0; if (load_properties_from_file) { HELIUM_ASSERT( IsValid( pRequest->asyncFileLoadId ) ); if ( !rAsyncLoader.TrySyncRequest( pRequest->asyncFileLoadId, bytesRead ) ) { return false; } } /////// POINT OF NO RETURN: We *will* return true after this point, and the object *will* be finished preloading, /////// for good or for bad. SetInvalid(pRequest->asyncFileLoadId); bool object_creation_failure = false; // If we already had an existing object, make sure the type and template match. if( pObject ) { const AssetType* pExistingType = pObject->GetAssetType(); HELIUM_ASSERT( pExistingType ); if( pExistingType != pType ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "LoosePackageLoader: Cannot load \"%s\" using the existing object as the types do not match " ) TXT( "(existing type: \"%s\"; serialized type: \"%s\".\n" ) ), *rObjectData.objectPath.ToString(), *pExistingType->GetName(), *pType->GetName() ); pObject->SetFlags( Asset::FLAG_PRELOADED | Asset::FLAG_LINKED ); pObject->ConditionalFinalizeLoad(); object_creation_failure = true; } } else { bool bCreateResult = false; if (pRequest->forceReload) { // Create the object. bCreateResult = Asset::CreateObject( pRequest->spObject, pType, Name( NULL_NAME ), NULL, pTemplate ); } else { // Create the object. bCreateResult = Asset::CreateObject( pRequest->spObject, pType, rObjectData.objectPath.GetName(), pOwner, pTemplate ); } if( !bCreateResult ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader: Failed to create \"%s\" during loading.\n" ), *rObjectData.objectPath.ToString() ); object_creation_failure = true; } pObject = pRequest->spObject; HELIUM_ASSERT( pObject ); } if (load_properties_from_file && !object_creation_failure) { // Sanity checks for file load, then success path HELIUM_ASSERT( bytesRead == pRequest->asyncFileLoadBufferSize ); if( IsInvalid( bytesRead ) ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader: Failed to read the contents of object file \"%s\" in async load request \"%d\".\n" ), object_file_path.c_str(), pRequest->asyncFileLoadId ); } else if( bytesRead != pRequest->asyncFileLoadBufferSize ) { HELIUM_TRACE( TraceLevels::Warning, ( TXT( "LoosePackageLoader: Attempted to read %" ) PRIuSZ TXT( " bytes from object file \"%s\", " ) TXT( "but only %" ) PRIuSZ TXT( " bytes were read.\n" ) ), pRequest->asyncFileLoadBufferSize, object_file_path.c_str(), bytesRead ); } else { StaticMemoryStream archiveStream ( pRequest->pAsyncFileLoadBuffer, pRequest->asyncFileLoadBufferSize ); HELIUM_TRACE( TraceLevels::Info, TXT( "LoosePackageLoader: Reading %s. pResolver = %x\n"), object_file_path.c_str(), pRequest->pResolver); DynamicArray< Reflect::ObjectPtr > objects; objects.Push( pRequest->spObject.Get() ); // use existing objects Persist::ArchiveReaderJson::ReadFromStream( archiveStream, objects, pRequest->pResolver ); HELIUM_ASSERT( objects[0].Get() == pRequest->spObject.Get() ); } } if (load_properties_from_file) { DefaultAllocator().Free(pRequest->pAsyncFileLoadBuffer); pRequest->pAsyncFileLoadBuffer = NULL; pRequest->asyncFileLoadBufferSize = 0; } pRequest->flags |= LOAD_FLAG_PROPERTY_PRELOADED; if( object_creation_failure ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoader: Deserialization of object \"%s\" failed.\n" ), *rObjectData.objectPath.ToString() ); pObject->SetFlags( Asset::FLAG_PRELOADED | Asset::FLAG_LINKED ); pObject->ConditionalFinalizeLoad(); pRequest->flags |= LOAD_FLAG_ERROR; } else if( !pObject->IsDefaultTemplate() ) { // If the object is a resource (not including the default template object for resource types), attempt to begin // loading any existing persistent resource data stored in the object cache. Resource* pResource = Reflect::SafeCast< Resource >( pObject ); if( pResource ) { Name objectCacheName = Name( HELIUM_ASSET_CACHE_NAME ); CacheManager& rCacheManager = CacheManager::GetStaticInstance(); Cache* pCache = rCacheManager.GetCache( objectCacheName ); HELIUM_ASSERT( pCache ); pCache->EnforceTocLoad(); const Cache::Entry* pEntry = pCache->FindEntry( rObjectData.objectPath, 0 ); if( pEntry && pEntry->size != 0 ) { HELIUM_ASSERT( IsInvalid( pRequest->persistentResourceDataLoadId ) ); HELIUM_ASSERT( !pRequest->pCachedObjectDataBuffer ); pRequest->pCachedObjectDataBuffer = static_cast< uint8_t* >( DefaultAllocator().Allocate( pEntry->size ) ); HELIUM_ASSERT( pRequest->pCachedObjectDataBuffer ); pRequest->cachedObjectDataBufferSize = pEntry->size; AsyncLoader& rAsyncLoader = AsyncLoader::GetStaticInstance(); pRequest->persistentResourceDataLoadId = rAsyncLoader.QueueRequest( pRequest->pCachedObjectDataBuffer, pCache->GetCacheFileName(), pEntry->offset, pEntry->size ); HELIUM_ASSERT( IsValid( pRequest->persistentResourceDataLoadId ) ); } } } if( IsInvalid( pRequest->persistentResourceDataLoadId ) ) { // No persistent resource data needs to be loaded. pObject->SetFlags( Asset::FLAG_PRELOADED ); pRequest->flags |= LOAD_FLAG_PERSISTENT_RESOURCE_PRELOADED; } // Asset is now preloaded. return true; }
/// Initialize this system. /// /// @param[in] rCommandLineInitialization Interface for initializing command-line parameters. /// @param[in] rMemoryHeapPreInitialization Interface for performing any necessary pre-initialization of dynamic /// memory heaps. /// @param[in] rAssetLoaderInitialization Interface for creating and initializing the main AssetLoader instance. /// Note that this must remain valid until Shutdown() is called on this /// system, as a reference to it will be held by this system. /// @param[in] rConfigInitialization Interface for initializing application configuration settings. /// @param[in] rWindowManagerInitialization Interface for creating and initializing the global window manager /// instance. /// @param[in] rRendererInitialization Interface for creating and initializing the global renderer instance. /// @param[in] pWorldType Type of World to create for the main world. If this is null, the /// actual World type will be used. bool GameSystem::Initialize( CommandLineInitialization& rCommandLineInitialization, MemoryHeapPreInitialization& rMemoryHeapPreInitialization, AssetLoaderInitialization& rAssetLoaderInitialization, ConfigInitialization& rConfigInitialization, WindowManagerInitialization& rWindowManagerInitialization, RendererInitialization& rRendererInitialization, AssetPath &rSystemDefinitionPath) { // Initialize the timer first of all, in case someone wants to use it. Timer::StaticInitialize(); // Initialize command-line parameters. bool bCommandLineInitSuccess = rCommandLineInitialization.Initialize( m_moduleName, m_arguments ); HELIUM_ASSERT( bCommandLineInitSuccess ); if( !bCommandLineInitSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Command-line initialization failed.\n" ) ); return false; } #if HELIUM_ENABLE_TRACE HELIUM_TRACE( TraceLevels::Info, TXT( "Module name: %s\n" ), *m_moduleName ); HELIUM_TRACE( TraceLevels::Info, TXT( "Command-line arguments:\n" ) ); size_t argumentCount = m_arguments.GetSize(); for( size_t argumentIndex = 0; argumentIndex < argumentCount; ++argumentIndex ) { HELIUM_TRACE( TraceLevels::Info, TXT( "* %s\n" ), *m_arguments[ argumentIndex ] ); } #endif // Initialize the async loading thread. bool bAsyncLoaderInitSuccess = AsyncLoader::GetStaticInstance().Initialize(); HELIUM_ASSERT( bAsyncLoaderInitSuccess ); if( !bAsyncLoaderInitSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Async loader initialization failed.\n" ) ); return false; } //pmd - Initialize the cache manager FilePath baseDirectory; if ( !FileLocations::GetBaseDirectory( baseDirectory ) ) { HELIUM_TRACE( TraceLevels::Error, TXT( "Could not get base directory." ) ); return false; } HELIUM_VERIFY( CacheManager::InitializeStaticInstance( baseDirectory ) ); // Initialize the reflection type registry and register Asset-based types. Reflect::Initialize(); Components::Initialize(); TaskScheduler::CalculateSchedule(); // Perform dynamic memory heap pre-initialization. rMemoryHeapPreInitialization.PreInitialize(); // Create and initialize the main AssetLoader instance. AssetLoader* pAssetLoader = rAssetLoaderInitialization.Initialize(); HELIUM_ASSERT( pAssetLoader ); if( !pAssetLoader ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Asset loader initialization failed.\n" ) ); return false; } m_pAssetLoaderInitialization = &rAssetLoaderInitialization; // Initialize system configuration. bool bConfigInitSuccess = rConfigInitialization.Initialize(); HELIUM_ASSERT( bConfigInitSuccess ); if( !bConfigInitSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Failed to initialize configuration settings.\n" ) ); return false; } if ( !rSystemDefinitionPath.IsEmpty() ) { pAssetLoader->LoadObject<SystemDefinition>( rSystemDefinitionPath, m_spSystemDefinition ); if ( !m_spSystemDefinition ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Could not find SystemDefinition. LoadObject on '%s' failed.\n" ), *rSystemDefinitionPath.ToString() ); } else { m_spSystemDefinition->Initialize(); } } // Initialize the job manager. bool bJobManagerInitSuccess = JobManager::GetStaticInstance().Initialize(); HELIUM_ASSERT( bJobManagerInitSuccess ); if( !bJobManagerInitSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Job manager initialization failed.\n" ) ); return false; } // Create and initialize the window manager (note that we need a window manager for message loop processing, so // the instance cannot be left null). bool bWindowManagerInitSuccess = rWindowManagerInitialization.Initialize(); HELIUM_ASSERT( bWindowManagerInitSuccess ); if( !bWindowManagerInitSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Window manager initialization failed.\n" ) ); return false; } // Create and initialize the renderer. bool bRendererInitSuccess = rRendererInitialization.Initialize(); HELIUM_ASSERT( bRendererInitSuccess ); if( !bRendererInitSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameSystem::Initialize(): Renderer initialization failed.\n" ) ); return false; } m_pRendererInitialization = &rRendererInitialization; // Initialize the world manager and main game world. WorldManager& rWorldManager = WorldManager::GetStaticInstance(); bool bWorldManagerInitSuccess = rWorldManager.Initialize(); HELIUM_ASSERT( bWorldManagerInitSuccess ); if( !bWorldManagerInitSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "World manager initialization failed.\n" ) ); return false; } // Initialization complete. return true; }
/// Get the package loader to use to load the object with the given path, creating it if necessary. /// /// @param[in] path Asset path. /// /// @return Package loader to use to load the specified object. LoosePackageLoader* LoosePackageLoaderMap::GetPackageLoader( AssetPath path ) { HELIUM_ASSERT( !path.IsEmpty() ); // Resolve the object's package. AssetPath packagePath = path; while( !packagePath.IsPackage() ) { packagePath = packagePath.GetParent(); if( packagePath.IsEmpty() ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "LoosePackageLoaderMap::GetPackageLoader(): Cannot resolve package loader for \"%s\", as it " ) TXT( "is not located in a package.\n" ) ), *path.ToString() ); return NULL; } } // Locate an existing package loader. ConcurrentHashMap< AssetPath, LoosePackageLoader* >::ConstAccessor constMapAccessor; if( m_packageLoaderMap.Find( constMapAccessor, packagePath ) ) { LoosePackageLoader* pLoader = constMapAccessor->Second(); HELIUM_ASSERT( pLoader ); return pLoader; } // Add a new package loader entry. ConcurrentHashMap< AssetPath, LoosePackageLoader* >::Accessor mapAccessor; bool bInserted = m_packageLoaderMap.Insert( mapAccessor, KeyValue< AssetPath, LoosePackageLoader* >( packagePath, NULL ) ); if( bInserted ) { // Entry added, so create and initialize the package loader. LoosePackageLoader* pLoader = new LoosePackageLoader; HELIUM_ASSERT( pLoader ); bool bInitResult = pLoader->Initialize( packagePath ); HELIUM_ASSERT( bInitResult ); if( !bInitResult ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LoosePackageLoaderMap::GetPackageLoader(): Failed to initialize package loader for \"%s\".\n" ), *packagePath.ToString() ); m_packageLoaderMap.Remove( mapAccessor ); return NULL; } HELIUM_VERIFY( pLoader->BeginPreload() ); mapAccessor->Second() = pLoader; } // PMD: I am not 100% sure this is all thread safe. I think it could break if: // - Thread 1 inserts a key/value of path and NULL, as above // - Thread 2 returns false from the insert so we come straight here // - Thread 2 tries to use pLoader before thread 1 assigns ploader to the value of the key // Note: If we did not do this assert here, we could potentially get nulls from this same thread later when // trying to find pLoader. // // Leaving it alone for now since I'm not sure, but if this assert gets tripped, we need to revisit this. // Easy fix may be to allocate and construct (but don't completely init) an LoosePackageLoader, and try // to insert that directly rather than the null above. If insert succeeds, finish, else ditch our loader // and grab the one out of the array LoosePackageLoader* pLoader = mapAccessor->Second(); HELIUM_ASSERT( pLoader ); return pLoader; }
/// @copydoc AssetLoader::CacheObject() bool LooseAssetLoader::CacheObject( Asset* pAsset, bool bEvictPlatformPreprocessedResourceData ) { HELIUM_ASSERT( pAsset ); HELIUM_TRACE( TraceLevels::Info, TXT( "LooseAssetLoader::CacheObject(): Caching asset %s.\n" ), *pAsset->GetPath().ToString() ); // Don't cache broken objects or packages. if( pAsset->GetAnyFlagSet( Asset::FLAG_BROKEN ) || pAsset->IsPackage() ) { return false; } // Make sure we have an object preprocessor instance with which to cache the object. AssetPreprocessor* pAssetPreprocessor = AssetPreprocessor::GetStaticInstance(); if( !pAssetPreprocessor ) { HELIUM_TRACE( TraceLevels::Warning, TXT( "LooseAssetLoader::CacheObject(): Missing AssetPreprocessor to use for caching.\n" ) ); return false; } // User configuration objects should not be cached. AssetPath objectPath = pAsset->GetPath(); Config& rConfig = Config::GetStaticInstance(); // Only cache the files we care about if ( rConfig.IsAssetPathInUserConfigPackage(objectPath) ) { return false; } int64_t objectTimestamp = pAsset->GetAssetFileTimeStamp(); if( !pAsset->IsDefaultTemplate() ) { Resource* pResource = Reflect::SafeCast< Resource >( pAsset ); if( pResource ) { AssetPath baseResourcePath = pResource->GetPath(); HELIUM_ASSERT( !baseResourcePath.IsPackage() ); for( ; ; ) { AssetPath parentPath = baseResourcePath.GetParent(); if( parentPath.IsEmpty() || parentPath.IsPackage() ) { break; } baseResourcePath = parentPath; } FilePath sourceFilePath; if ( !FileLocations::GetDataDirectory( sourceFilePath ) ) { HELIUM_TRACE( TraceLevels::Warning, TXT( "LooseAssetLoader::CacheObject(): Could not obtain data directory.\n" ) ); return false; } sourceFilePath += baseResourcePath.ToFilePathString().GetData(); Status stat; stat.Read( sourceFilePath.Get().c_str() ); int64_t sourceFileTimestamp = stat.m_ModifiedTime; if( sourceFileTimestamp > objectTimestamp ) { objectTimestamp = sourceFileTimestamp; } } } // Cache the object. bool bSuccess = pAssetPreprocessor->CacheObject( pAsset, objectTimestamp, bEvictPlatformPreprocessedResourceData ); if( !bSuccess ) { HELIUM_TRACE( TraceLevels::Error, TXT( "LooseAssetLoader: Failed to cache object \"%s\".\n" ), *objectPath.ToString() ); } return bSuccess; }
/// Set this path to the combination of two paths. /// /// @param[in] pRootPath Root portion of the path. /// @param[in] subPath Sub-path component. /// /// @return True if the paths could be joined into a valid path (to which this path was set), false if joining was /// invalid. bool AssetPath::Join( const char* pRootPath, AssetPath subPath ) { if( !pRootPath || pRootPath[ 0 ] == TXT( '\0' ) ) { m_pEntry = subPath.m_pEntry; return true; } // Parse the root path into a series of names. StackMemoryHeap<>& rStackHeap = ThreadLocalStackAllocator::GetMemoryHeap(); StackMemoryHeap<>::Marker stackMarker( rStackHeap ); Name* pRootPathNames; uint32_t* pRootPathIndices; size_t rootPathNameCount; size_t rootPathPackageCount; if( !Parse( pRootPath, rStackHeap, pRootPathNames, pRootPathIndices, rootPathNameCount, rootPathPackageCount ) ) { return false; } if( rootPathNameCount != rootPathPackageCount ) { AssetPath testSubPathComponent = subPath.GetParent(); AssetPath subPathComponent; do { subPathComponent = testSubPathComponent; testSubPathComponent = testSubPathComponent.GetParent(); if( subPathComponent.IsPackage() ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "AssetPath::Join(): Cannot combine \"%s\" and \"%s\" (second path is rooted in a " ) TXT( "package, while the first path ends in an object).\n" ) ), pRootPath, *subPath.ToString() ); return false; } } while( !testSubPathComponent.IsEmpty() ); } // Assemble the list of path names in reverse order for performing the object path lookup/add. size_t nameCount = rootPathNameCount; size_t packageCount = rootPathPackageCount; AssetPath testPath; for( testPath = subPath; !testPath.IsEmpty(); testPath = testPath.GetParent() ) { ++nameCount; if( testPath.IsPackage() ) { ++packageCount; } } Name* pEntryNames = static_cast< Name* >( rStackHeap.Allocate( sizeof( Name ) * nameCount ) ); HELIUM_ASSERT( pEntryNames ); uint32_t* pInstanceIndices = static_cast< uint32_t* >( rStackHeap.Allocate( sizeof( uint32_t ) * nameCount ) ); HELIUM_ASSERT( pInstanceIndices ); Name* pCurrentName = pEntryNames; uint32_t* pCurrentIndex = pInstanceIndices; for( testPath = subPath; !testPath.IsEmpty(); testPath = testPath.GetParent() ) { *pCurrentName = testPath.GetName(); *pCurrentIndex = testPath.GetInstanceIndex(); ++pCurrentName; ++pCurrentIndex; } ArrayCopy( pCurrentName, pRootPathNames, rootPathNameCount ); ArrayCopy( pCurrentIndex, pRootPathIndices, rootPathNameCount ); // Set the path. Set( pEntryNames, pInstanceIndices, nameCount, packageCount ); return true; }