/// @copydoc Serializer::SerializeObjectReference() void BinarySerializer::SerializeObjectReference( const GameObjectType* /*pType*/, GameObjectPtr& rspObject ) { if( ShouldSerializeCurrentProperty() ) { HELIUM_ASSERT( m_bAllowObjectReferences ); if( !m_bAllowObjectReferences ) { HELIUM_TRACE( TRACE_ERROR, ( TXT( "BinarySerializer: Attempted to serialize an object reference using a serializer with " ) TXT( "which it is not allowed.\n" ) ) ); return; } uint32_t objectIndex; SetInvalid( objectIndex ); GameObject* pObject = rspObject.Get(); if( pObject ) { objectIndex = ResolveObjectDependency( pObject->GetPath() ); HELIUM_ASSERT( IsValid( objectIndex ) ); } m_pPropertyStream->Write( &objectIndex, sizeof( objectIndex ), 1 ); } }
/// @copydoc Serializer::Serialize() bool BinarySerializer::Serialize( GameObject* pObject ) { HELIUM_ASSERT( pObject ); BeginSerialize( true ); // Serialize the object type reference. const GameObjectType* pType = pObject->GetGameObjectType(); HELIUM_ASSERT( pType ); uint32_t typeIndex = ResolveTypeDependency( pType->GetName() ); HELIUM_ASSERT( IsValid( typeIndex ) ); m_pPropertyStream->Write( &typeIndex, sizeof( typeIndex ), 1 ); // Serialize the object template. uint32_t templateIndex; SetInvalid( templateIndex ); if( !pObject->IsDefaultTemplate() ) { GameObject* pTemplate = Reflect::AssertCast< GameObject >( pObject->GetTemplate() ); HELIUM_ASSERT( pTemplate ); templateIndex = ResolveObjectDependency( pTemplate->GetPath() ); HELIUM_ASSERT( IsValid( templateIndex ) ); } m_pPropertyStream->Write( &templateIndex, sizeof( templateIndex ), 1 ); // Serialize the object owner. uint32_t ownerIndex; SetInvalid( ownerIndex ); GameObject* pOwner = pObject->GetOwner(); if( pOwner ) { ownerIndex = ResolveObjectDependency( pOwner->GetPath() ); HELIUM_ASSERT( IsValid( ownerIndex ) ); } m_pPropertyStream->Write( &ownerIndex, sizeof( ownerIndex ), 1 ); // Serialize the object properties. pObject->Serialize( *this ); EndSerialize(); return true; }
/// @name Serializer::SerializeObjectReference() void GameObjectLoader::Linker::SerializeObjectReference( const GameObjectType* pType, GameObjectPtr& rspObject ) { HELIUM_ASSERT( pType ); uint32_t linkIndex = rspObject.GetLinkIndex(); rspObject.ClearLinkIndex(); if( IsInvalid( linkIndex ) ) { return; } if( linkIndex >= m_linkEntryCount ) { HELIUM_TRACE( TRACE_ERROR, TXT( "GameObjectLoader: Invalid link index %" ) TPRIu32 TXT( " encountered. Setting null reference.\n" ), linkIndex ); m_bError = true; return; } GameObject* pObject = m_pLinkEntries[ linkIndex ].spObject; if( pObject ) { if( !pObject->IsClass( pType ) ) { HELIUM_TRACE( TRACE_ERROR, TXT( "GameObjectLoader: GameObject reference \"%s\" is not of the correct type (\"%s\").\n" ), *pObject->GetPath().ToString(), *pType->GetName() ); m_bError = true; } else { rspObject = pObject; } } }
/// Update resource precaching for the given object load request. /// /// @param[in] pRequest Load request to update. /// /// @return True if resource precaching still requires processing, false if not. bool GameObjectLoader::TickPrecache( LoadRequest* pRequest ) { HELIUM_ASSERT( pRequest ); HELIUM_ASSERT( !( pRequest->stateFlags & LOAD_FLAG_LOADED ) ); GameObject* pObject = pRequest->spObject; if( pObject ) { // Wait for all link dependencies to fully load first. DynArray< LinkEntry >& rLinkTable = pRequest->linkTable; size_t linkTableSize = rLinkTable.GetSize(); for( size_t linkIndex = 0; linkIndex < linkTableSize; ++linkIndex ) { LinkEntry& rLinkEntry = rLinkTable[ linkIndex ]; if( IsValid( rLinkEntry.loadId ) ) { if( !TryFinishLoad( rLinkEntry.loadId, rLinkEntry.spObject ) ) { return false; } SetInvalid( rLinkEntry.loadId ); rLinkEntry.spObject.Release(); } } rLinkTable.Resize( 0 ); // Perform any pre-precaching work (note that we don't precache anything for the default template object for // a given type). OnPrecacheReady( pObject, pRequest->pPackageLoader ); if( !pObject->GetAnyFlagSet( GameObject::FLAG_BROKEN ) && !pObject->IsDefaultTemplate() && pObject->NeedsPrecacheResourceData() ) { if( !( pRequest->stateFlags & LOAD_FLAG_PRECACHE_STARTED ) ) { if( !pObject->BeginPrecacheResourceData() ) { HELIUM_TRACE( TRACE_ERROR, TXT( "GameObjectLoader: Failed to begin precaching object \"%s\".\n" ), *pObject->GetPath().ToString() ); pObject->SetFlags( GameObject::FLAG_PRECACHED | GameObject::FLAG_BROKEN ); AtomicOrRelease( pRequest->stateFlags, LOAD_FLAG_PRECACHED | LOAD_FLAG_ERROR ); return true; } AtomicOrRelease( pRequest->stateFlags, LOAD_FLAG_PRECACHE_STARTED ); } if( !pObject->TryFinishPrecacheResourceData() ) { return false; } } pObject->SetFlags( GameObject::FLAG_PRECACHED ); } AtomicOrRelease( pRequest->stateFlags, LOAD_FLAG_PRECACHED ); return true; }
/// Perform shutdown of the GameObject system. /// /// This releases all final references to objects and releases all allocated memory. This should be called during /// the shutdown process after all types have been unregistered as well as after calling GameObjectType::Shutdown(). /// /// @see GameObjectType::Shutdown() void GameObject::Shutdown() { HELIUM_TRACE( TraceLevels::Info, TXT( "Shutting down GameObject system.\n" ) ); GameObject::ReleaseStaticType(); #pragma TODO( "Fix support for casting between Reflect::Object and GameObject once the type systems have been properly integrated." ) #if HELIUM_ENABLE_MEMORY_TRACKING ConcurrentHashSet< RefCountProxy< Reflect::Object >* >::ConstAccessor refCountProxyAccessor; if( Reflect::ObjectRefCountSupport::GetFirstActiveProxy( refCountProxyAccessor ) ) { HELIUM_TRACE( TraceLevels::Error, TXT( "%" ) TPRIuSZ TXT( " smart pointer(s) still active during shutdown!\n" ), Reflect::ObjectRefCountSupport::GetActiveProxyCount() ); #if 1 refCountProxyAccessor.Release(); #else size_t activeGameObjectCount = 0; while( refCountProxyAccessor.IsValid() ) { RefCountProxy< Reflect::Object >* pProxy = *refCountProxyAccessor; HELIUM_ASSERT( pProxy ); GameObject* pGameObject = Reflect::SafeCast< GameObject >( pProxy->GetObject() ); if( pGameObject ) { ++activeGameObjectCount; } ++refCountProxyAccessor; } HELIUM_TRACE( TraceLevels::Error, TXT( "%" ) TPRIuSZ TXT( " active GameObject smart pointer(s):\n" ), activeGameObjectCount ); Reflect::ObjectRefCountSupport::GetFirstActiveProxy( refCountProxyAccessor ); while( refCountProxyAccessor.IsValid() ) { RefCountProxy< Reflect::Object >* pProxy = *refCountProxyAccessor; HELIUM_ASSERT( pProxy ); GameObject* pGameObject = Reflect::SafeCast< GameObject >( pProxy->GetObject() ); if( pGameObject ) { HELIUM_TRACE( TraceLevels::Error, TXT( "- 0x%p: %s (%" ) TPRIu16 TXT( " strong ref(s), %" ) TPRIu16 TXT( " weak ref(s))\n" ), pProxy, ( pGameObject ? *pGameObject->GetPath().ToString() : TXT( "(cleared reference)" ) ), pProxy->GetStrongRefCount(), pProxy->GetWeakRefCount() ); } ++refCountProxyAccessor; } #endif } #endif // HELIUM_ENABLE_MEMORY_TRACKING #if !HELIUM_RELEASE size_t objectCountActual = sm_objects.GetUsedSize(); if( objectCountActual != 0 ) { HELIUM_TRACE( TraceLevels::Error, TXT( "%" ) TPRIuSZ TXT( " object(s) still referenced during shutdown!\n" ), objectCountActual ); size_t objectCount = sm_objects.GetSize(); for( size_t objectIndex = 0; objectIndex < objectCount; ++objectIndex ) { if( !sm_objects.IsElementValid( objectIndex ) ) { continue; } GameObject* pObject = sm_objects[ objectIndex ]; if( !pObject ) { continue; } HELIUM_TRACE( TraceLevels::Error, TXT( "- %s\n" ), *pObject->GetPath().ToString() ); } } #endif // !HELIUM_RELEASE sm_objects.Clear(); sm_wpFirstTopLevelObject.Release(); delete sm_pNameInstanceIndexMap; sm_pNameInstanceIndexMap = NULL; delete sm_pEmptyNameInstanceIndexMap; sm_pEmptyNameInstanceIndexMap = NULL; delete sm_pEmptyInstanceIndexSet; sm_pEmptyInstanceIndexSet = NULL; sm_serializationBuffer.Clear(); }
/// Modify the name, owner, or instance index of this object. /// /// @param[in] rParameters Object rename parameters. /// /// @return True if this object was renamed successfully, false if not. /// /// @see GetName(), GetOwner(), GetInstanceIndex() bool GameObject::Rename( const RenameParameters& rParameters ) { Name name = rParameters.name; GameObject* pOwner = rParameters.spOwner; uint32_t instanceIndex = rParameters.instanceIndex; HELIUM_TRACE( TraceLevels::Debug, TXT("GameObject::Rename(): Renaming object \"%s\" to \"%s\" (Old Owner: \"%s\". New Owner: \"%s\".)\n"), *m_name, *rParameters.name, m_spOwner.ReferencesObject() ? *m_spOwner->GetPath().ToString() : TXT("[none]"), rParameters.spOwner.ReferencesObject() ? *rParameters.spOwner->GetPath().ToString() : TXT("[none]")); // Only allow setting an empty name if no owner or instance index are given and this object has no children. if( name.IsEmpty() ) { HELIUM_ASSERT( !pOwner ); HELIUM_ASSERT( IsInvalid( instanceIndex ) ); if( pOwner || IsValid( instanceIndex ) ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "GameObject::Rename(): Objects cannot have name information cleared if being assigned an " ) TXT( "owner or instance index.\n" ) ) ); return false; } HELIUM_ASSERT( !m_wpFirstChild ); if( m_wpFirstChild ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameObject::Rename(): Cannot clear name information for objects with children.\n" ) ); return false; } } // Don't allow setting the owner to ourself. if( pOwner == this ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameObject::Rename(): Cannot set the owner of an object to itself.\n" ) ); return false; } // Don't allow setting the owner to an object with no name information. if( pOwner && pOwner->m_name.IsEmpty() ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameObject::Rename(): Cannot set the owner of an object to an object with no path information.\n" ) ); return false; } if( IsPackage() ) { // Don't allow package objects to be children of non-package objects. if( pOwner && !pOwner->IsPackage() ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameObject::Rename(): Cannot set a non-package as the owner of a package.\n" ) ); return false; } // Don't allow instance indexing for packages. if( IsValid( instanceIndex ) ) { HELIUM_TRACE( TraceLevels::Error, TXT( "GameObject::Rename(): Instance indexing not supported for packages.\n" ) ); return false; } } // Don't need to do anything if the name, owner, and instance index are not changing. if( name == m_name && pOwner == m_spOwner && ( instanceIndex == m_instanceIndex || ( instanceIndex == INSTANCE_INDEX_AUTO && IsValid( m_instanceIndex ) ) ) ) { return true; } // Hold onto a reference to the current owner until we return from this function. This is done in case this object // has the last strong reference to it, in which case we would encounter a deadlock if clearing its reference while // we still have a write lock on the object list (object destruction also requires acquiring a write lock). GameObjectPtr spOldOwner = m_spOwner; { // Acquire a write lock on the object list to prevent objects from being added and removed as well as keep // objects from being renamed while this object is being renamed. ScopeWriteLock scopeLock( sm_objectListLock ); // Get the list of children belonging to the new owner. GameObjectWPtr& rwpOwnerFirstChild = ( pOwner ? pOwner->m_wpFirstChild : sm_wpFirstTopLevelObject ); // Don't check for name clashes if we're clearing the object path name information. if( !name.IsEmpty() ) { // Resolve name clashes either through the instance index lookup map (if an instance index will be assigned) // or through a child object search (if no instance index will be used). if( IsValid( instanceIndex ) ) { // Get the instance index map for the requested object name. ChildNameInstanceIndexMap& rNameInstanceIndexMap = GetNameInstanceIndexMap(); HELIUM_ASSERT( sm_pEmptyNameInstanceIndexMap ); HELIUM_ASSERT( sm_pEmptyInstanceIndexSet ); sm_pEmptyNameInstanceIndexMap->First() = ( pOwner ? pOwner->GetPath() : GameObjectPath( NULL_NAME ) ); sm_pEmptyInstanceIndexSet->First() = name; ChildNameInstanceIndexMap::Accessor childNameMapAccessor; rNameInstanceIndexMap.Insert( childNameMapAccessor, *sm_pEmptyNameInstanceIndexMap ); NameInstanceIndexMap::Accessor indexSetAccessor; childNameMapAccessor->Second().Insert( indexSetAccessor, *sm_pEmptyInstanceIndexSet ); InstanceIndexSet& rIndexSet = indexSetAccessor->Second(); InstanceIndexSet::ConstAccessor indexAccessor; if( instanceIndex == INSTANCE_INDEX_AUTO ) { // Pick an unused instance index. instanceIndex = 0; while( !rIndexSet.Insert( indexAccessor, instanceIndex ) ) { ++instanceIndex; HELIUM_ASSERT( instanceIndex < INSTANCE_INDEX_AUTO ); } } else { // Attempt to acquire the specified instance index. if( !rIndexSet.Insert( indexAccessor, instanceIndex ) ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "GameObject::Rename(): Object already exists with the specified owner (%s), name " ) TXT( "(%s), and instance index (%" ) TPRIu32 TXT( ").\n" ) ), ( pOwner ? *pOwner->GetPath().ToString() : TXT( "none" ) ), *name, instanceIndex ); return false; } } } else { // Check each child of the new owner for a name clash. for( GameObject* pChild = rwpOwnerFirstChild; pChild != NULL; pChild = pChild->m_wpNextSibling ) { if( pChild->m_name == name && pChild->m_instanceIndex == instanceIndex ) { HELIUM_TRACE( TraceLevels::Error, ( TXT( "GameObject::Rename(): Object already exists with the specified owner (%s) and " ) TXT( "name (%s).\n" ) ), ( pOwner ? *pOwner->GetPath().ToString() : TXT( "none" ) ), *name ); return false; } } } } // Remove any old instance index tracking for the old path name. if( IsValid( m_instanceIndex ) ) { GameObjectPath ownerPath = ( spOldOwner ? spOldOwner->GetPath() : GameObjectPath( NULL_NAME ) ); ChildNameInstanceIndexMap& rNameInstanceIndexMap = GetNameInstanceIndexMap(); ChildNameInstanceIndexMap::Accessor childMapAccessor; HELIUM_VERIFY( rNameInstanceIndexMap.Find( childMapAccessor, ownerPath ) ); NameInstanceIndexMap& rNameMap = childMapAccessor->Second(); NameInstanceIndexMap::Accessor nameMapAccessor; HELIUM_VERIFY( rNameMap.Find( nameMapAccessor, m_name ) ); InstanceIndexSet& rIndexSet = nameMapAccessor->Second(); HELIUM_VERIFY( rIndexSet.Remove( m_instanceIndex ) ); /* if( rIndexSet.IsEmpty() ) { HELIUM_VERIFY( rNameMap.Remove( nameMapAccessor ) ); if( rNameMap.IsEmpty() ) { HELIUM_VERIFY( rNameInstanceIndexMap.Remove( childMapAccessor ) ); } } */ } // If the owner of this object is changing, remove this object from its old owner's list and add it to the new // owner. if( spOldOwner.Get() != pOwner || ( m_name.IsEmpty() ? !name.IsEmpty() : name.IsEmpty() ) ) { // Object should not be in any child object lists if its name is empty. if( !m_name.IsEmpty() ) { GameObjectWPtr& rwpOldOwnerFirstChild = ( spOldOwner ? spOldOwner->m_wpFirstChild : sm_wpFirstTopLevelObject ); GameObject* pPreviousChild = NULL; GameObject* pChild = rwpOldOwnerFirstChild; while( pChild ) { if( pChild == this ) { ( pPreviousChild ? pPreviousChild->m_wpNextSibling : rwpOldOwnerFirstChild ) = m_wpNextSibling; m_wpNextSibling.Release(); break; } pPreviousChild = pChild; pChild = pChild->m_wpNextSibling; } } HELIUM_ASSERT( !m_wpNextSibling ); // Only store the object in a child object list if it is being given a valid name. if( !name.IsEmpty() ) { m_wpNextSibling = rwpOwnerFirstChild; rwpOwnerFirstChild = this; } } // Set the new path name. m_name = name; m_spOwner = pOwner; m_instanceIndex = instanceIndex; // Update path information for this object and its children. UpdatePath(); } return true; }
/// @copydoc PackageLoader::TryFinishLoadObject() bool CachePackageLoader::TryFinishLoadObject( size_t requestId, GameObjectPtr& rspObject, DynamicArray< GameObjectLoader::LinkEntry >& rLinkTable ) { HELIUM_ASSERT( requestId < m_loadRequests.GetSize() ); HELIUM_ASSERT( m_loadRequests.IsElementValid( requestId ) ); LoadRequest* pRequest = m_loadRequests[ requestId ]; HELIUM_ASSERT( pRequest ); if( !( pRequest->flags & LOAD_FLAG_PRELOADED ) ) { return false; } // Sync on template and owner dependencies. GameObjectLoader* pObjectLoader = GameObjectLoader::GetStaticInstance(); HELIUM_ASSERT( pObjectLoader ); DynamicArray< size_t >& rInternalLinkTable = pRequest->objectLinkTable; if( IsValid( pRequest->templateLinkIndex ) ) { size_t linkLoadId = rInternalLinkTable[ pRequest->templateLinkIndex ]; if( IsValid( linkLoadId ) && !pObjectLoader->TryFinishLoad( linkLoadId, pRequest->spTemplate ) ) { return false; } SetInvalid( pRequest->templateLinkIndex ); } if( IsValid( pRequest->ownerLinkIndex ) ) { size_t linkLoadId = rInternalLinkTable[ pRequest->ownerLinkIndex ]; if( IsValid( linkLoadId ) && !pObjectLoader->TryFinishLoad( linkLoadId, pRequest->spOwner ) ) { return false; } SetInvalid( pRequest->ownerLinkIndex ); } rspObject = pRequest->spObject; GameObject* pObject = rspObject; if( pObject && ( pRequest->flags & LOAD_FLAG_ERROR ) ) { pObject->SetFlags( GameObject::FLAG_BROKEN ); } pRequest->spObject.Release(); size_t linkTableSize = rInternalLinkTable.GetSize(); rLinkTable.Resize( 0 ); rLinkTable.Reserve( linkTableSize ); for( size_t linkIndex = 0; linkIndex < linkTableSize; ++linkIndex ) { GameObjectLoader::LinkEntry* pEntry = rLinkTable.New(); HELIUM_ASSERT( pEntry ); pEntry->loadId = rInternalLinkTable[ linkIndex ]; pEntry->spObject.Release(); } rInternalLinkTable.Resize( 0 ); HELIUM_ASSERT( IsInvalid( pRequest->asyncLoadId ) ); HELIUM_ASSERT( !pRequest->pAsyncLoadBuffer ); pRequest->spType.Release(); pRequest->spTemplate.Release(); pRequest->spOwner.Release(); pRequest->typeLinkTable.Resize( 0 ); HELIUM_ASSERT( pObject || pRequest->pEntry ); HELIUM_TRACE( TraceLevels::Debug, ( TXT( "CachePackageLoader::TryFinishLoadObject(): Load request for \"%s\" (ID: %" ) TPRIuSZ TXT( ") " ) TXT( "synced.\n" ) ), *( pObject ? pObject->GetPath() : pRequest->pEntry->path ).ToString(), requestId ); m_loadRequests.Remove( requestId ); m_loadRequestPool.Release( pRequest ); return true; }