Files
GTASource/rage/scaleform/Src/GFxPlayer/GFxMovieDef.cpp

2631 lines
92 KiB
C++
Raw Normal View History

2025-02-23 17:40:52 +08:00
/**********************************************************************
Filename : GFxMovieDef.cpp
Content : SWF Player Core movie data structures.
Created :
Authors :
Copyright : (c) 2001-2006 Scaleform Corp. All Rights Reserved.
Notes : This file contains class declarations used in
GFxPlayerImpl.cpp only. Declarations that need to be
visible by other player files should be placed
in GFxCharacter.h.
Licensees may use this file in accordance with the valid Scaleform
Commercial License Agreement provided with the software.
This file is provided AS IS with NO WARRANTY OF ANY KIND, INCLUDING
THE WARRANTY OF DESIGN, MERCHANTABILITY AND FITNESS FOR ANY PURPOSE.
**********************************************************************/
#include "GFxMovieDef.h"
#include "GFxLoadProcess.h"
#include "GFile.h"
// For GFxSprite and GFxMovieRoot
#include "GFxSprite.h"
#include "GFxPlayerImpl.h"
#include "GFxText.h"
#include "GFxFontGlyphPacker.h"
#include "GFxFontManager.h"
#include "GFxImagePacker.h"
#include "GFxShape.h"
#include "GFxSound.h"
#include "GASAsFunctionObject.h"
#include "GFxAmpServer.h"
#include "AMP/GFxAmpViewStats.h"
// This is the key string that will cause import substitution
// to use GFxFontLib instead in order to support internationalization.
#define GFX_FONTLIB_IMPORT_KEY "gfxfontlib.swf"
// ***** GFxDataAllocator
// Allocation class used for tag data in GFxMovieDataDef.
GFxDataAllocator::GFxDataAllocator(GMemoryHeap* pheap)
: pHeap(pheap)
{
pAllocations = 0;
pCurrent = 0;
BytesLeft = 0;
}
GFxDataAllocator::~GFxDataAllocator()
{
while(pAllocations)
{
Block* pnext = pAllocations->pNext;
GHEAP_FREE(pHeap, pAllocations);
pAllocations = pnext;
}
}
void* GFxDataAllocator::OverflowAlloc(UPInt bytes)
{
// If the request is big, give it an individual chunk.
if (bytes > BlockSize / 2)
return AllocIndividual(bytes);
if (bytes > BytesLeft)
{
Block* pblock = (Block*) GHEAP_ALLOC(pHeap, BlockSize + sizeof(Block), GFxStatMD_Tags_Mem);
if (!pblock)
return 0;
// Insert to allocated list.
pblock->pNext = pAllocations;
pAllocations = pblock;
// Assign free space.
pCurrent = (UByte*) (pblock + 1);
BytesLeft= BlockSize;
}
void* pmem = pCurrent;
pCurrent += bytes;
BytesLeft -= bytes;
return pmem;
}
void* GFxDataAllocator::AllocIndividual(UPInt bytes)
{
Block* pblock = (Block*) GHEAP_ALLOC(pHeap, bytes + sizeof(Block), GFxStatMD_Tags_Mem);
if (!pblock)
return 0;
// Insert to allocated list.
pblock->pNext = pAllocations;
pAllocations = pblock;
return (pblock + 1);
}
// ***** GFxMovieDataDef
GFxMovieDataDef::GFxMovieDataDef(const GFxResourceKey &creationKey,
MovieDataType mtype, const char *purl,
GMemoryHeap* pargHeap, bool debugHeap, UPInt memoryArena)
: ResourceKey(creationKey), MovieType(mtype)
{
// printf("Thr %4d, %8x : GFxMovieDataDef()\n", GetCurrentThreadId(), this);
// LoadTaskData gets its own heap, if it loaded separately. It is not possible
// to put GFxMovieDataDef into the same heap since destruction order is not known,
// so we don't know to which object we should attach heap destroy.
GMemoryHeap* pheap = pargHeap;
if (!pheap)
{
GString heapName("MovieData \"", G_GetShortFilename(purl), "\"");
UInt heapFlags = debugHeap ? GMemoryHeap::Heap_UserDebug : 0;
GMemoryHeap::HeapDesc desc(heapFlags);
desc.Flags |= GMemoryHeap::Heap_FixedGranularity;
desc.Granularity = 8*1024;
desc.Reserve = 0;
desc.HeapId = GHeapId_MovieData;
desc.Arena = memoryArena;
pheap = GMemory::GetGlobalHeap()->CreateHeap(heapName.ToCStr(), desc);
}
pData = *GHEAP_NEW(pheap) LoadTaskData(this, purl, pheap);
if (!pargHeap)
pheap->ReleaseOnFree(pData.GetPtr());
}
GFxMovieDataDef::~GFxMovieDataDef()
{
//printf("Thr %4d, %8x : ~GFxMovieDataDef()\n", GetCurrentThreadId(), this);
// Notify LoadDataTask about our death.
pData->OnMovieDataDefRelease();
}
// ~DefBindingData is executed as a part of ~GFxMovieDataDef, it is
// responsible for calling destructors on BindData linked list content
// that was allocated from tag allocator.
GFxMovieDataDef::DefBindingData::~DefBindingData()
{
// Destroy bind frames.
GFxFrameBindData* pframeData = pFrameData;
pFrameData = 0;
while(pframeData)
{
GFxFrameBindData* pframeNext = pframeData->pNextFrame;
pframeData->~GFxFrameBindData();
pframeData = pframeNext;
}
// Destroy imports.
while(pImports)
{
GFxImportData *pimport = pImports;
pImports = pImports->pNext;
pimport->~GFxImportData();
}
// Destroy bind data.
while(pResourceNodes)
{
GFxResourceDataNode* pnode = pResourceNodes;
pResourceNodes = pResourceNodes->pNext;
pnode->~GFxResourceDataNode();
}
// Destroy fonts data nodes.
while(pFonts)
{
GFxFontDataUseNode* pnode = pFonts;
pFonts = pFonts->pNext;
pnode->~GFxFontDataUseNode();
}
}
// Helper used to look up labeled frames and/or translate frame numbers from a string.
bool GFxMovieDataDef::TranslateFrameString(
const GFxStringHashLH<UInt> &namedFrames,
const char* label, UInt* frameNumber, bool translateNumbers)
{
if (!label || !label[0])
return 0;
// First, determines if a string is a frame number. Generally,
// if it is NOT a frame number, the string is treated as a label.
// However, there are some cases when a string number check is
// not done (i.e. op 0x8C), so that is treated as an option.
if (translateNumbers)
{
// The string must evaluate to an integer to be converted to
// frame number; frames like "8.5" are treated as labels. Whitespace,
// '+' or '-' characters, however, are allowed in front of a number string.
// However, any other characters in the string would cause it to be a label.
// TBD: May be that matches ECMA ActionScript number conversion ?
bool digitFound = 0;
int i;
// Check whether to interpret as a number or a label.
for (i=0; label[i] != 0; i++)
{
UByte ch = label[i];
if ((ch>='0') && (ch <= '9'))
{
digitFound = 1;
continue;
}
if (ch == '+' || ch == '-')
{
if (!digitFound)
continue;
}
if (ch == ' ' || ch == '\t')
continue;
// Any other char? It's a label.
goto translate_frame_label;
}
// This must be a frame number: do conversion.
char* tail = 0;
UInt number = (UInt) G_strtod(label, &tail);
// Check for conversion failure.
if (tail == label || *tail != 0)
return 0;
// Frames number arguments start with 1, so make it 0-based.
*frameNumber = number - 1;
return 1;
}
translate_frame_label:
// We have found a label, do lookup.
return namedFrames.GetCaseInsensitive(GString(label), frameNumber);
}
// *** GFxMovieDataDef::LoadTaskData
GFxMovieDataDef::LoadTaskData::LoadTaskData(GFxMovieDataDef* pdataDef, const char *purl,
GMemoryHeap* pheap)
: LoadTaskDataBase(pheap), pHeap(pheap),
FileURL(purl), GradientIdGenerator(GFxResourceId::IdType_GradientImage), SwdHandle(0)
#ifndef GFC_NO_SOUND
, pSoundStream(NULL)
#endif
{
// We don't store the pDataDef pointer because it is unsafe if
// GFxMovieDataDef dies in main thread. Just use it for GFxSpriteDef.
FileAttributes = 0;
pMetadata = 0;
MetadataSize = 0;
pPathAllocator = GHEAP_NEW(pHeap) GFxPathAllocator();
LoadingCanceled = false;
LoadState = LS_Uninitialized;
LoadingFrame = 0;
TagCount = 0; // Should be based on export?
ResIndexCounter = 0;
InitActionsCnt = 0;
#ifndef GFC_NO_THREADSUPPORT
pFrameUpdate = *GNEW GFxLoadUpdateSync();
#endif
// Add an empty character id, for createEmptyMovieClip.
GPtr<GFxSpriteDef> pdef = *GHEAP_NEW(pHeap) GFxSpriteDef(pdataDef);
pdef->InitEmptyClipDef();
AddResource(GFxResourceId(GFxCharacterDef::CharId_EmptyMovieClip), pdef);
GPtr<GFxEditTextCharacterDef> ptextDef = *GHEAP_NEW(pHeap) GFxEditTextCharacterDef();
ptextDef->InitEmptyTextDef();
AddResource(GFxResourceId(GFxCharacterDef::CharId_EmptyTextField), ptextDef);
}
GFxMovieDataDef::LoadTaskData::~LoadTaskData()
{
// Wait for the lock in case if anther object is still accessing this
ResourceLocker lock(this);
// Destroy frame data.
// Should not need to lock playlist here since when destroying MovieDef /
// LoadTaskData loading must have completed and playback is finished.
UInt i;
for(i=0; i<Playlist.GetSize(); i++)
Playlist[i].DestroyTags();
for(i=0; i<InitActionList.GetSize(); i++)
InitActionList[i].DestroyTags();
if (pPathAllocator)
delete pPathAllocator;
if (pMetadata)
{
GHEAP_FREE(pHeap, pMetadata);
pMetadata = 0;
}
#ifndef GFC_NO_SOUND
if (pSoundStream)
pSoundStream->Release();
#endif // GFC_NO_SOUND
}
// Notifies LoadTaskData that MovieDataDef is being destroyed. This may be a
// premature destruction if we are in the process of loading (in that case it
// will lead to loading being canceled).
void GFxMovieDataDef::LoadTaskData::OnMovieDataDefRelease()
{
if (LoadState <= LS_LoadingFrames)
LoadingCanceled = true;
}
// Initialize an empty movie definition.
void GFxMovieDataDef::LoadTaskData::InitEmptyMovieDef()
{
GASSERT(LoadState == LS_Uninitialized);
// FrameCount of 1 is the default for an empty clip.
GASSERT(Header.FrameCount == 1);
{ // Not critical if GFxMovieDataDef is never used for playback while
// InitEmptyMovieDef hasn't been called; but lock for consistency.
GLock::Locker lock(&PlaylistLock);
Playlist.Resize(Header.FrameCount);
InitActionList.Resize(Header.FrameCount);
InitActionsCnt = 0;
}
UpdateLoadState(Header.FrameCount, LS_LoadFinished);
}
// Create a definition describing an image file.
void GFxMovieDataDef::LoadTaskData::InitImageFileMovieDef(
UInt fileLength, GFxImageResource *pimageResource, bool bilinear)
{
//GASSERT(LoadState == LS_Uninitialized);
// Original FrameCount of 1 is critical for proper image load waiting.
GASSERT(Header.FrameCount == 1);
Header.FileLength = fileLength;
// Create image resource and shape character using it.
AddResource(GFxResourceId(CharId_ImageMovieDef_ImageResource), pimageResource);
GPtr<GFxShapeCharacterDef> pshapeDef = *GHEAP_NEW(pHeap) GFxShapeCharacterDef();
pshapeDef->SetToImage(pimageResource, bilinear);
AddCharacter(GFxResourceId(CharId_ImageMovieDef_ShapeDef), pshapeDef);
// Configure us to have one frame - same as empty initialization.
{
GLock::Locker lock(&PlaylistLock);
Playlist.Resize(Header.FrameCount);
InitActionList.Resize(Header.FrameCount);
InitActionsCnt = 0;
GASSERT(Playlist.GetSize() == 1);
// Add a PlaceObject command, so that it creates a shape.
// We use an individual allocator since we don't want a block of memory to be wasted.
void* ptagMem = TagMemAllocator.AllocIndividual(
sizeof(GFxPlaceObjectUnpacked) + sizeof(GASExecuteTag*));
if (ptagMem)
{
GASExecuteTag** pptagArray = (GASExecuteTag**) ptagMem;
GFxPlaceObjectUnpacked* ptag = (GFxPlaceObjectUnpacked*) (pptagArray + 1);
G_Construct<GFxPlaceObjectUnpacked>(ptag);
// Verified: Flash assigns depth -16383 to image shapes (depth value = 1 in our list).
SInt depth = 1;
GFxCharPosInfo pos(GFxResourceId(GFxCharacterDef::CharId_ImageMovieDef_ShapeDef),
depth, 0, GRenderer::Cxform(), 1, GRenderer::Matrix());
ptag->InitializeToAdd(pos);
// Add playlist frame.
GASSERT(Playlist.GetSize() == 1);
pptagArray[0] = ptag;
Playlist[0].pTagPtrList = pptagArray;
Playlist[0].TagCount = 1;
}
else
{
// Not enough memory for tags.
GASSERT(0);
}
}
// Notify of load completion.
UpdateLoadState(Header.FrameCount, LS_LoadFinished);
}
// Return heap used for image data of this movie; creates the heap if it doesn't exist.
GMemoryHeap* GFxMovieDataDef::LoadTaskData::GetImageHeap()
{
if (!pImageHeap)
{
GMemoryHeap::HeapDesc desc;
desc.Flags |= GMemoryHeap::Heap_FixedGranularity;
desc.MinAlign = 32;
desc.Granularity = 4 * 1024;
desc.Reserve = 0;
desc.Threshold = ~UPInt(0);
desc.Limit = 0;
desc.HeapId = GHeapId_Images;
pImageHeap = *pHeap->CreateHeap("_Images", desc);
}
return pImageHeap.GetPtr();
}
// Initializes SWF/GFX header for loading.
void GFxMovieDataDef::LoadTaskData::BeginSWFLoading(const GFxMovieHeaderData &header)
{
Header = header;
UpdateLoadState(LoadingFrame, LS_LoadingFrames);
}
// Read a .SWF pMovie.
void GFxMovieDataDef::LoadTaskData::Read(GFxLoadProcess *plp, GFxMovieBindProcess* pbp)
{
GASSERT(LoadState == LS_LoadingFrames);
// Reference stream for easy access.
GFxStream &stream = *plp->GetStream();
// Create list and process tags
{
GLock::Locker lock(&PlaylistLock);
Playlist.Resize(Header.FrameCount);
InitActionList.Resize(Header.FrameCount);
}
stream.LogParseClass(Header.FrameRect);
stream.LogParse("Note: SWF Frame Rate = %f, Frames = %d\n",
Header.FPS, Header.FrameCount);
// Initialize LoadProcess lists(?)
// -> they should be already empty, since each process is only used once
TagCount = 0;
// Accumulate tag bytes to know when to send notify to binding thread.
// We compute update increment based on total file size so that we
// don't get too few (bad progress) or too many notifications (slow
// due to expensive thread switching on OS).
int tagLoadedBytes = 0;
bool notifyNeeded = false;
int loadUpdateIncrement = (Header.FileLength / 30);
if (loadUpdateIncrement < 1024*8)
loadUpdateIncrement = 1024*8;
const GFxSWFProcessInfo &pi = plp->GetProcessInfo();
while ((UInt32) stream.Tell() < pi.FileEndPos)
{
// Cancel loading if requested externally.
if (LoadingCanceled)
{
// There may be frame tags left; clean them up.
plp->CleanupFrameTags();
if (pbp)
pbp->SetBindState(GFxMovieDefImpl::BS_Canceled);
UpdateLoadState(LoadingFrame, LS_LoadCanceled);
return;
}
GFxTagInfo tagInfo;
GFxTagType tagType = stream.OpenTag(&tagInfo);
#ifdef GFC_DEBUG_COUNT_TAGS
plp->CountTag(tagType);
#endif
tagLoadedBytes += tagInfo.TagLength;
// Report previous progress to sleeping/waiting binding threads
// once a while (more efficient then doing it every frame).
// Update waiters if a lot of data was loaded or if the next
// tag is big (i.e. could cause slow load).
#ifndef GFC_NO_THREADSUPPORT
if (notifyNeeded &&
((LoadingFrame == 1) ||
(tagLoadedBytes > loadUpdateIncrement) ||
(tagInfo.TagLength > 1024*8)))
{
pFrameUpdate->UpdateNotify();
notifyNeeded = false;
tagLoadedBytes = 0;
}
#endif
plp->ReportProgress(FileURL, tagInfo, false);
GFxLoaderImpl::LoaderFunction lf = NULL;
//in->LogParse("tagType = %d\n", tagType);
if (tagType == GFxTag_EndFrame)
{
// We handle EndFrame logic after the tag is closed below.
// That is helpful to ensure that reported bytesLoaded position is correct.
}
else if (GFxLoaderImpl::GetTagLoader(tagType, &lf))
{
/* Tag decoding helper code
// if ((tagType == 83) || (tagType == 2) || (tagType == 32) ||
// Morph
// (tagType == 46) || (tagType == 84) )
{
int start = stream.Tell();
stream.LogParse("*** Tag data for tag %d:\n", tagType);
stream.LogTagBytes();
stream.SetPosition(start);
}
*/
// call the tag loader. The tag loader should add
// characters or tags to the GFxMovieSub data structure.
(*lf)(plp, tagInfo);
}
else
{
// no tag loader for this tag type.
stream.LogParse("*** no tag loader for type %d\n", tagType);
stream.LogTagBytes();
}
stream.CloseTag();
TagCount++;
// Handle EndFrame after it's tag is closed.
if (tagType == GFxTag_EndFrame)
{
// Post bound frame and update count.
if (!FinishLoadingFrame(plp, false))
{
// Error occurred; LS_LoadError should already be set internally.
return;
}
notifyNeeded = true;
// Show frame tag -- advance to the next frame.
stream.LogParse(" ShowFrame\n");
// Transfer loaded resources to frame
// If binding was requested, do binding.
if (pbp)
{
pbp->BindNextFrame();
// Do we need to do anything about bind errors? Technically
// they will be sticky in the bind process.
}
}
else if (tagType == 0)
{
if ((unsigned)stream.Tell() != pi.FileEndPos)
{
// Safety break, so we don't read past the end of the pMovie.
stream.LogWarning("Warning: GFxLoader - GFxStream-end tag hit, but not at the "
"end of the file yet; stopping for safety\n");
break;
}
}
}
// TBD: Shouldn't we verify the number of frames loaded
// and set a different state otherwise?
if (plp->FrameTagsAvailable())
{
FinishLoadingFrame(plp, true);
if (pbp)
pbp->BindNextFrame();
// Do we need to do anything about bind errors? Technically
// they will be sticky in the bind process.
}
else
{
// Change state to LS_LoadFinished with notification.
UpdateLoadState(LoadingFrame, LS_LoadFinished);
}
}
// Updates bind data and increments LoadingFrame.
// It is expected that it will also release any threads waiting on it in the future.
bool GFxMovieDataDef::LoadTaskData::FinishLoadingFrame(GFxLoadProcess *plp, bool finished)
{
plp->CommitFrameTags();
// TBD: We could record a pending error status in GFxLoadProcess
// and check it here...
bool success = true;
GFxFrameBindData* pfdata = plp->CreateFrameBindData();
if (pfdata)
{
// Record the number of resources in frame so that they can be bound.
pfdata->Frame = LoadingFrame;
pfdata->BytesLoaded = plp->GetStream()->Tell() -
plp->GetProcessInfo().FileStartPos;
}
#ifndef GFC_NO_THREADSUPPORT
GMutex::Locker lock(&pFrameUpdate->GetMutex());
#endif
if (pfdata)
{
// Update the front if necessary and insert item at the end.
if (!BindData.pFrameData)
BindData.pFrameData = pfdata;
else
BindData.pFrameDataLast->pNextFrame = pfdata;
BindData.pFrameDataLast = pfdata;
LoadingFrame++;
if (finished)
LoadState = LS_LoadFinished;
}
else
{
LoadState = LS_LoadError;
success = false;
}
// Notify bind thread(s) that file has finished loading.
// More detailed per-frame data notifications take place in the main
// loop but only once substantial enough chunk is read.
#ifndef GFC_NO_THREADSUPPORT
//printf("Next frame is ready %d\n", LoadingFrame);
if (finished || !success)
pFrameUpdate->UpdateNotify();
#endif
return success;
}
// Update frame and loading state with proper notification, for use image loading, etc.
void GFxMovieDataDef::LoadTaskData::UpdateLoadState(UInt loadingFrame, MovieLoadState loadState)
{
#ifndef GFC_NO_THREADSUPPORT
GMutex::Locker lock(&pFrameUpdate->GetMutex());
LoadingFrame = loadingFrame;
LoadState = loadState;
pFrameUpdate->UpdateNotify();
#else
LoadingFrame = loadingFrame;
LoadState = loadState;
#endif
}
void GFxMovieDataDef::LoadTaskData::NotifyFrameUpdated()
{
#ifndef GFC_NO_THREADSUPPORT
GMutex::Locker lock(&pFrameUpdate->GetMutex());
pFrameUpdate->UpdateNotify();
#endif
}
// Waits until loading is completed, used by GFxFontLib.
void GFxMovieDataDef::LoadTaskData::WaitForLoadFinish()
{
#ifndef GFC_NO_THREADSUPPORT
if (LoadState <= LS_LoadingFrames)
{
pFrameUpdate->WaitForLoadFinished();
// LoadState must've been modified before LoadFinished flag is set.
GASSERT(LoadState > LS_LoadingFrames);
}
#endif
}
// Waits until loading of a given frame is completed
void GFxMovieDataDef::LoadTaskData::WaitForFrame(UInt frame)
{
#ifndef GFC_NO_THREADSUPPORT
if (LoadState <= LS_LoadingFrames && LoadingFrame <= frame)
{
GMutex::Locker lock(&pFrameUpdate->GetMutex());
while (LoadState <= LS_LoadingFrames && LoadingFrame <= frame)
pFrameUpdate->WaitForNotify();
}
#else
GUNUSED(frame);
#endif
}
UInt GFxMovieDataDef::LoadTaskData::GetMetadata(char *pbuff, UInt buffSize) const
{
if (!pbuff)
return MetadataSize;
buffSize = G_Min<UInt>(buffSize, MetadataSize);
if (pMetadata)
memcpy(pbuff, pMetadata, buffSize);
return buffSize;
}
// Sets MetaData of desired size.
void GFxMovieDataDef::LoadTaskData::SetMetadata(UByte *pdata, UInt size)
{
// Should only set metadata once.
GASSERT(pMetadata == 0);
if ((pMetadata = (UByte*)GHEAP_ALLOC(pHeap, size, GFxStatMD_Other_Mem))!=0)
{
MetadataSize = size;
memcpy(pMetadata, pdata, size);
}
}
// Add a resource during loading.
void GFxMovieDataDef::LoadTaskData::AddResource(GFxResourceId rid, GFxResource* pres)
{
ResourceLocker lock(this);
Resources.Add(rid, GFxResourceHandle(pres));
}
// Creates a new resource handle with a binding index for the resourceId; used
// to register ResourceData objects that need to be bound later.
GFxResourceHandle GFxMovieDataDef::LoadTaskData::AddNewResourceHandle(GFxResourceId rid)
{
GFxResourceHandle rh(GFxResourceHandle::RH_Index, ResIndexCounter);
ResIndexCounter++;
ResourceLocker lock(this);
Resources.Add(rid, rh);
return rh;
}
bool GFxMovieDataDef::LoadTaskData::GetResourceHandle(GFxResourceHandle* phandle, GFxResourceId rid) const
{
ResourceLocker lock(this);
ResourceHash::ConstIterator ir = Resources.Find(rid);
if (ir != Resources.End())
{
*phandle = ir->Second;
return 1;
}
return 0;
}
// Get font data bi ResourceId.
GFxFont* GFxMovieDataDef::LoadTaskData::GetFontData(GFxResourceId rid)
{
GFxFontDataUseNode* pfonts = GetFirstFont();
// Thread-safe traversal since only completed fonts are added to
// list. TBD: We might want to check that loading has completed.
while(pfonts)
{
if (pfonts->Id == rid)
return pfonts->pFontData.GetPtr();
pfonts = pfonts->pNext;
}
return 0;
}
// Labels the frame currently being loaded with the given name.
// A copy of the name string is made and kept in this object.
void GFxMovieDataDef::LoadTaskData::AddFrameName(const GString& name, GFxLog *plog)
{
GUNUSED(plog);
GASSERT(LoadingFrame < Header.FrameCount);
GLock::Locker lock(&PlaylistLock);
NamedFrames.SetCaseInsensitive(name, LoadingFrame); // stores 0-based frame #
}
void GFxMovieDataDef::LoadTaskData::SetLoadingPlaylistFrame(const Frame& frame)
{
// We should not call SetLoadingPlaylistFrame() for the same frame multiple times.
GLock::Locker lock(&PlaylistLock);
GASSERT(Playlist[LoadingFrame].TagCount == 0);
Playlist[LoadingFrame] = frame;
}
void GFxMovieDataDef::LoadTaskData::SetLoadingInitActionFrame(const Frame& frame)
{
GLock::Locker lock(&PlaylistLock);
GASSERT(InitActionList[LoadingFrame].TagCount == 0);
InitActionList[LoadingFrame] = frame;
++InitActionsCnt;
}
// Expose one of our resources under the given symbol,
// for export. Other movies can import it.
void GFxMovieDataDef::LoadTaskData::ExportResource(const GString& symbol, GFxResourceId rid, const GFxResourceHandle &hres)
{
ResourceLocker lock(this);
// SWF sometimes exports the same thing more than once!
// Export names are case-insensitive.
Exports.SetCaseInsensitive(symbol, hres);
GASSERT(Exports.FindCaseInsensitive(symbol) != Exports.End());
InvExports.Set(rid, Exports.FindCaseInsensitive(symbol)->First);
}
// Returns 0-based frame #
bool GFxMovieDataDef::LoadTaskData::GetLabeledFrame(const char* label, UInt* frameNumber, bool translateNumbers)
{
if (LoadState >= LS_LoadFinished)
return TranslateFrameString(NamedFrames, label, frameNumber, translateNumbers);
// NamedFrames access is synchronized if loading is not finished.
GLock::Locker lock(&PlaylistLock);
return TranslateFrameString(NamedFrames, label, frameNumber, translateNumbers);
}
const GFxTimelineDef::Frame GFxMovieDataDef::LoadTaskData::GetPlaylist(int frameNumber) const
{
if (LoadState >= LS_LoadFinished)
return Playlist[frameNumber];
// Only lock access if we are still loading.
// This lock shouldn't be a big problem because most playlists
// will come from GFxSprite where they don't need to be locked.
// However, we could still get a bottleneck here due to FindPreviousPlaceObject2.
// If this happens, we might have to come up with a buffered multi-frame copy solution.
GLock::Locker lock(&PlaylistLock);
return Playlist[frameNumber];
}
bool GFxMovieDataDef::LoadTaskData::GetInitActions(Frame* pframe, int frameNumber) const
{
GLock::Locker lock(&PlaylistLock);
if (((UInt)frameNumber) >= InitActionList.GetSize())
return false;
*pframe = InitActionList[frameNumber];
return true;
}
UInt GFxMovieDataDef::LoadTaskData::GetInitActionListSize() const
{
GLock::Locker lock(&PlaylistLock);
return (UInt)InitActionList.GetSize();
}
#ifndef GFC_NO_SOUND
GFxSoundStreamDef* GFxMovieDataDef::LoadTaskData::GetSoundStream() const
{
return pSoundStream;
}
void GFxMovieDataDef::LoadTaskData::SetSoundStream(GFxSoundStreamDef* psoundStream)
{
if (pSoundStream)
pSoundStream->Release();
if (psoundStream)
psoundStream->AddRef();
pSoundStream = psoundStream;
}
#endif
// *** Creating MovieDefData file keys
// GFxMovieDataDef key: {FileName, pFileOpener, optional pImageCreator}
class GFxMovieDataDefFileKeyData : public GRefCountBase<GFxMovieDataDefFileKeyData, GStat_Default_Mem>
{
friend class GFxMovieDataDefFileKeyInterface;
GString FileName;
GPtr<GFxFileOpener> pFileOpener;
SInt64 ModifyTime;
// pImageCreator is unique in that it is only used for GFxMovieDataDef binding
// if GFxImageCreator::IsKeepingImageData returns false and GFxLoader::LoadKeepBindData
// flag is not specified. If one of those flags are set, GFxImageCreator is only
// used as an argument for GFxMovieDefImpl and not MovieDataDef. In that case,
// its value here will be null and image resources are not created until binding time.
GPtr<GFxImageCreator> pImageCreator;
GPtr<GFxPreprocessParams> pPreprocessParams;
public:
GFxMovieDataDefFileKeyData(const char* pfilename, SInt64 modifyTime,
GFxFileOpener* pfileOpener, GFxImageCreator *pimageCreator,
GFxPreprocessParams* ppreprocessParams)
{
FileName = pfilename;
ModifyTime = modifyTime;
pFileOpener = pfileOpener;
pImageCreator = pimageCreator;
pPreprocessParams = ppreprocessParams;
}
bool operator == (GFxMovieDataDefFileKeyData& other) const
{
return pFileOpener == other.pFileOpener &&
pImageCreator == other.pImageCreator &&
ModifyTime == other.ModifyTime &&
FileName == other.FileName &&
pPreprocessParams == other.pPreprocessParams;
}
bool operator != (GFxMovieDataDefFileKeyData& other) const
{
return !operator == (other);
}
UPInt GetHashCode() const
{
UPInt fileHashCode = GString::BernsteinHashFunction(FileName.ToCStr(),
FileName.GetSize());
return fileHashCode ^
((UPInt)ModifyTime) ^
((UPInt)pFileOpener.GetPtr()) ^ (((UPInt)pFileOpener.GetPtr()) >> 7) ^
((UPInt)pImageCreator.GetPtr()) ^ (((UPInt)pImageCreator.GetPtr()) >> 7) ^
((UPInt)pPreprocessParams.GetPtr()) ^ (((UPInt)pPreprocessParams.GetPtr()) >> 7);
}
};
class GFxMovieDataDefFileKeyInterface : public GFxResourceKey::KeyInterface
{
public:
typedef GFxResourceKey::KeyHandle KeyHandle;
virtual void AddRef(KeyHandle hdata)
{
GASSERT(hdata); ((GFxMovieDataDefFileKeyData*) hdata)->AddRef();
}
virtual void Release(KeyHandle hdata)
{
GASSERT(hdata); ((GFxMovieDataDefFileKeyData*) hdata)->Release();
}
// Key/Hash code implementation.
virtual GFxResourceKey::KeyType GetKeyType(KeyHandle hdata) const
{
GUNUSED(hdata);
return GFxResourceKey::Key_File;
}
virtual UPInt GetHashCode(KeyHandle hdata) const
{
GASSERT(hdata);
return ((GFxMovieDataDefFileKeyData*) hdata)->GetHashCode();
}
virtual bool KeyEquals(KeyHandle hdata, const GFxResourceKey& other)
{
if (this != other.GetKeyInterface())
return 0;
return *((GFxMovieDataDefFileKeyData*) hdata) ==
*((GFxMovieDataDefFileKeyData*) other.GetKeyData());
}
const char* GetFileURL(KeyHandle hdata) const
{
GFxMovieDataDefFileKeyData* pdata = (GFxMovieDataDefFileKeyData*) hdata;
GASSERT(pdata);
return pdata->FileName.ToCStr();
}
};
static GFxMovieDataDefFileKeyInterface GFxMovieDataDefFileKeyInterface_Instance;
// Create a key for an SWF file corresponding to GFxMovieDef.
GFxResourceKey GFxMovieDataDef::CreateMovieFileKey(const char* pfilename,
SInt64 modifyTime,
GFxFileOpener* pfileOpener,
GFxImageCreator* pimageCreator,
GFxPreprocessParams* ppreprocessParams)
{
GPtr<GFxMovieDataDefFileKeyData> pdata =
*GNEW GFxMovieDataDefFileKeyData(pfilename, modifyTime, pfileOpener,
pimageCreator, ppreprocessParams);
return GFxResourceKey(&GFxMovieDataDefFileKeyInterface_Instance,
(GFxResourceKey::KeyHandle)pdata.GetPtr() );
}
GString GFxMovieDataDef::GetResourceName() const
{
//return GString("MovieData \"", G_GetShortFilename(GetFileURL()), "\"");
return GString(G_GetShortFilename(GetFileURL()));
}
//
// ***** GFxMovieDefImpl
//
GFxMovieDefImpl::GFxMovieDefImpl(GFxMovieDataDef* pdataDef,
GFxMovieDefBindStates* pstates,
GFxLoaderImpl* ploaderImpl,
UInt loadConstantFlags,
GFxStateBagImpl *pdelegateState,
GMemoryHeap* pargHeap,
bool fullyLoaded,
UPInt memoryArena)
{
//printf("GFxMovieDefImpl::GFxMovieDefImpl: %x, thread : %d\n", this, GetCurrentThreadId());
GMemoryHeap* pheap = pargHeap;
if (!pheap)
{
// Heap name above includes extra space to align with "Movie Data".
GString heapName("MovieDef \"", G_GetShortFilename(pdataDef->GetFileURL()), "\"");
UInt heapFlags = (loadConstantFlags & GFxLoader::LoadDebugHeap) ?
GMemoryHeap::Heap_UserDebug : 0;
// Binding heap gets small granularity since its initially empty.
GMemoryHeap::HeapDesc desc(heapFlags, 16, 4*1024, 4*1024);
desc.HeapId = GHeapId_MovieDef;
desc.Arena = memoryArena;
pheap = GMemory::GetGlobalHeap()->CreateHeap(heapName.ToCStr(), desc);
}
pBindData = *GHEAP_NEW(pheap) BindTaskData(pheap, pdataDef, this,
loadConstantFlags, fullyLoaded);
if (!pargHeap)
pheap->ReleaseOnFree(pBindData.GetPtr());
pLoaderImpl = ploaderImpl;
// We MUST have states and DataDef
GASSERT(pstates);
pBindStates = pstates;
// Create a delegated shared state.
pStateBag = *GNEW GFxStateBagImpl(pdelegateState);
}
GFxMovieDefImpl::~GFxMovieDefImpl()
{
pBindData->OnMovieDefRelease();
}
// *** GFxMovieDefBindProcess - used for GFxMovieDefImpl Binding
GFxMovieBindProcess::GFxMovieBindProcess(GFxLoadStates *pls,
GFxMovieDefImpl* pdefImpl,
GFxLoaderImpl::LoadStackItem* ploadStack)
: GFxLoaderTask(pls, Id_MovieBind),
pFrameBindData(0), GlyphTextureIdGen(GFxResourceId::IdType_DynFontImage),
/*pLoadStates(pls), */pBindData(pdefImpl->pBindData), pLoadStack(ploadStack)
{
// RefCountMode is already thread safe for GFxTask.
pDataDef = pBindData->GetDataDef();
Stripped = ((pDataDef->GetSWFFlags() & GFxMovieInfo::SWF_Stripped) != 0);
GFxImagePackParamsBase* pimagePacker = pls->GetBindStates()->pImagePackParams;
if (pimagePacker && !Stripped)
{
GFxImageCreateInfo icreateInfo(NULL, GFxResource::Use_Bitmap);
icreateInfo.SetStates(0, pls->GetRenderConfig(), pls->GetLog(), NULL,NULL);
icreateInfo.ThreadedLoading = pls->IsThreadedLoading();
icreateInfo.pHeap = pdefImpl->GetBindDataHeap();
pImagePacker = *pimagePacker->Begin(&GlyphTextureIdGen, pls->GetBindStates()->pImageCreator, &icreateInfo);
pImagePacker->SetBindData(pBindData);
pTempBindData = GNEW GFxTempBindData;
}
else
pTempBindData = 0;
}
GFxMovieBindProcess::~GFxMovieBindProcess()
{
#ifndef GFC_NO_THREADSUPPORT
GPtr<GFxLoadUpdateSync> ploadSync = pBindData ? pBindData->GetBindUpdateSync() : 0;
#endif
if (pBindData)
{
if (pBindData->GetBindState() == GFxMovieDefImpl::BS_InProgress)
pBindData->SetBindState(GFxMovieDefImpl::BS_Canceled);
pBindData = 0;
}
if (pTempBindData)
delete pTempBindData;
pImagePacker = 0;
#ifndef GFC_NO_THREADSUPPORT
// Notify any waiters that our task thread has released
// all of its references to loaded data.
if (ploadSync)
ploadSync->NotifyLoadFinished();
#endif
}
GFxMovieBindProcess::BindStateType GFxMovieBindProcess::BindNextFrame()
{
// For some reason this check seems to be required, as we
// may com in here with null pointer after cancel. This check
// prevents GetBindStateType() from crashing.
if (!pBindData)
return GFxMovieDefImpl::BS_Canceled;
// Check binding state and return correct value if appropriate.
BindStateType startBindStateType = GetBindStateType();
if (startBindStateType != GFxMovieDefImpl::BS_InProgress)
{
if (startBindStateType == GFxMovieDefImpl::BS_NotStarted)
{
SetBindState(GFxMovieDefImpl::BS_InProgress|GetBindStateFlags());
// Binding can only take place once.
GASSERT(pBindData->BindingFrame == 0);
}
else
{
return startBindStateType;
}
}
// Get the next frame data if possible, waiting for it if necessary.
// The nature of frames linked list allows us to avoid mutex locks
// on every access. This is ok as long as updates happen within the
// lock on the producer (DataDef) thread side.
GFxFrameBindData* pframeData = GetNextFrameBindData();
#ifndef GFC_NO_THREADSUPPORT
if (!pframeData)
{
GFxMovieDataDef::LoadTaskData* ploadData = pDataDef->pData;
GMutex::Locker lock(&ploadData->pFrameUpdate->GetMutex());
pframeData = GetNextFrameBindData();
// Wait until next frame data is available or loading fails.
//printf("Waiting for the next frame: %p\n", this);
while ((pframeData == 0) &&
(ploadData->LoadState == GFxMovieDataDef::LS_LoadingFrames) &&
!pBindData->BindingCanceled)
{
ploadData->pFrameUpdate->WaitForNotify();
pframeData = GetNextFrameBindData();
};
//printf("Done waiting for the next frame: %p\n", this);
if (ploadData->LoadState == GFxMovieDataDef::LS_LoadCanceled)
pBindData->BindingCanceled = true;
}
#endif
if (!pframeData || pBindData->BindingCanceled)
{
// Error occurred in load, turn it into error in processing.
FinishBinding();
BindStateType bindState = pBindData->BindingCanceled ?
GFxMovieDefImpl::BS_Canceled : GFxMovieDefImpl::BS_Error;
SetBindState(bindState | GetBindStateFlags());
// If binding was canceled, release pBindData so that the load process
// can terminate as well (necessary if we are on the same thread).
#ifndef GFC_NO_THREADSUPPORT
GPtr<GFxLoadUpdateSync> psync = pBindData->GetBindUpdateSync();
pBindData = 0;
psync->NotifyLoadFinished();
#else
pBindData = 0;
#endif
return bindState;
}
// At this point we must be processing binding frame (unless we
// allow empty frame entries in the future). Not that allowing
// such entries would cause problem with waits & BindingFrame
// updates, unless done intelligently.
GASSERT(pBindData->BindingFrame == pframeData->Frame);
pFrameBindData = pframeData;
// Perform the main tasks of binding:
// 1. Resolve all imports and store imported movie references
// in ImportSourceMovies.
// 2. Populate the ResourceBinding table to resolve Handles by using
// the resource binding data.
// 3. Process/pack fonts included in this frame.
// 4. Update frame data and bytes.
GFxLoadStates *pls = pLoadStates;
UInt loadFlags = pBindData->LoadFlags;
bool fontImportsSucceeded = true;
// Resolve all imports for this frame.
if (!(loadFlags & GFxLoader::LoadDisableImports))
{
UInt iimport;
GFxImportData* pimportData = pframeData->pImportData;
// Iterate imports for this frame. We must limit the number of
// imports iterated and not go till the end of the list because
// the list contains ALL imports in the file.
for (iimport = 0; iimport < pframeData->ImportCount;
iimport++, pimportData = pimportData->pNext)
{
// These must hold at this point.
GASSERT(pimportData != 0);
GASSERT(pimportData->Frame <= pBindData->BindingFrame);
const GString& sourceURL = pimportData->SourceUrl;
// See if the source URL ends in 'gfxfontlib.swf', in which
// case we do import font substitution instead.
static const char fontlibKey[] = GFX_FONTLIB_IMPORT_KEY;
const UPInt fontlibKeySize = sizeof(fontlibKey) - 1;
if ((sourceURL.GetSize() >= fontlibKeySize) &&
(GString::CompareNoCase(sourceURL.ToCStr() +
(sourceURL.GetSize() - fontlibKeySize), fontlibKey) == 0))
{
// This if a substituted font import, handle it in a custom manner.
bool allSucceed = false;
// Grab temporary reference to GFxMovieDefImpl to access pSharedSate
GPtr<GFxMovieDefImpl> pdefImpl = *pBindData->GetMovieDefImplAddRef();
if (pdefImpl)
{
// Check if FontLib is installed. if it is not or import name is not "$IMECandidateListFont"
// we will treat 'gfxfontlib.swf' file like a usual import file.
GPtr<GFxFontLib> pfontLib = pdefImpl->GetFontLib();
if (pfontLib.GetPtr()
#if defined(GFC_USE_IME)
||(pimportData->Imports.GetSize() > 0 && pimportData->Imports[0].SymbolName == "$IMECandidateListFont" )
#endif
)
{
allSucceed = pBindData->ResolveImportThroughFontLib(pimportData);
if (!allSucceed)
fontImportsSucceeded = 0;
// TO DO: Font import substitution could cause font resource to be assigned
// to resource data slots of different type (import, etc.) in pathological
// cases. We should check for that in the future perhaps by verifying the
// type during binding load time...
continue;
}
}
else
continue;
}
GFxMovieDefImpl* pdef = 0;
// LoadStates are separate in import because import has a separate MovieDef.
GPtr<GFxLoadStates> pimportLoadStates = *pls->CloneForImport();
// Load flags must be the same, but we wait for load completion with imports.
UInt importLoadFlags = loadFlags | GFxLoader::LoadWaitCompletion;
// Create import stack. We need it for detecting recursion int loading process.
// It is safe to create the item on stack because we set LoadWaitCompletion
// load state and call to CreateMovie_LoadState will not return until the whole movie
// with all its dependencies if any is loaded.
GFxLoaderImpl::LoadStackItem loadStack(pBindData->pDefImpl_Unsafe);
if (!pLoadStack)
pLoadStack = &loadStack;
else
{
GFxLoaderImpl::LoadStackItem* ptail = pLoadStack;
while(ptail->pNext)
ptail = ptail->pNext;
ptail->pNext = &loadStack;
}
// Try both SWF and GFX files.
// If the file is stripped, its imports may be stripped too,
// so try to load '.gfx' import file first.
// We don't use GetLength() because ".swf" is represented as 4 bytes in UTF8
if (Stripped && (sourceURL.GetSize() > 4) &&
(GString::CompareNoCase(sourceURL.ToCStr() +
(sourceURL.GetSize() - 4), ".swf") == 0) )
{
GFxURLBuilder::LocationInfo loc(GFxURLBuilder::File_Import,
sourceURL, pls->GetRelativePath());
loc.FileName.Clear();
loc.FileName.AppendString(sourceURL.ToCStr(), sourceURL.GetSize() - 4);
loc.FileName += ".gfx";
// Use our captured load state to load the imported movie. This
// guarantees that the states are consistent.
pdef = GFxLoaderImpl::CreateMovie_LoadState(pimportLoadStates,
loc, importLoadFlags, pLoadStack);
}
if (!pdef)
{
GFxURLBuilder::LocationInfo loc(GFxURLBuilder::File_Import,
sourceURL, pls->GetRelativePath());
pdef = GFxLoaderImpl::CreateMovie_LoadState(pimportLoadStates,
loc, importLoadFlags, pLoadStack);
}
// Remove stack item from the stack
if (pLoadStack == &loadStack)
pLoadStack = NULL;
else
{
GFxLoaderImpl::LoadStackItem* ptail = pLoadStack;
while(ptail->pNext)
{
if (ptail->pNext == &loadStack)
{
ptail->pNext = ptail->pNext->pNext;
break;
}
ptail = ptail->pNext;
}
GASSERT(!ptail->pNext);
}
if (!pdef)
{
// Failed loading; leave us at correct bind frame.
FinishBinding();
SetBindState(GFxMovieDefImpl::BS_Error | GetBindStateFlags());
return GFxMovieDefImpl::BS_Error;
}
else
{
// It is possible for SWF files to import themselves, which Flash
// seems to handle gracefully. What happens with indirect recursiveness?
// TBD: For now we the pass the recursive flag to avoid leaks,
// but we will need to investigate this in detail in the future.
bool recursive = (pdef->pBindData == pBindData);
if (recursive && pls->GetLog())
pls->GetLog()->LogWarning("Warning: Self recursive import detected in '%s'\n",
sourceURL.ToCStr());
pBindData->ResolveImport(pimportData, pdef, pls, recursive);
// Invoke default import visitor to notify it of resolve.
GFxImportVisitor* pimportVistor = pls->GetBindStates()->pImportVisitor;
if (pimportVistor)
{
// Do temporary locked grab of our pDefImpl pointer.
GPtr<GFxMovieDefImpl> pdefImpl = *pBindData->GetMovieDefImplAddRef();
if (pdefImpl)
pimportVistor->Visit(pdefImpl, pdef, sourceURL.ToCStr());
}
pdef->Release();
}
} // for (iimport...)
}
// Create & Resolve all resources in the frame.
GFxResourceDataNode* presData = pframeData->pResourceData;
UInt ires;
GFxResourceBinding &resourceBinding = pBindData->ResourceBinding;
GMemoryHeap* pheap = pBindData->GetBindDataHeap();
for (ires = 0; ires < pframeData->ResourceCount; ires++, presData = presData->pNext)
{
GASSERT(presData != 0);
if (!presData->Data.IsValid())
{
// We can get here for several reasons:
// 1) This presData->BindIndex corresponds to an imported resource that failed
// to load. In that case we would've already emitted a warning inside of ResolveImport.
// 2) An image loading JPEG/ZLib Support was not installed causing image
// resource data to be null.
// For now, leave the slot as null.
// MA NOTE: There used to be a non-null import slot with ASSERT here that was triggered
// only if fontImportsSucceeded was true, but is seems to serve no purpose.
GUNUSED(fontImportsSucceeded);
}
else
{
// Create a resource based on its loaded data in DataDef.
GFxResourceBindData bd;
bd.pBinding = &resourceBinding;
bd.pResource= 0;
if (presData->Data.CreateResource(&bd, pls, pheap))
{
if (pImagePacker && bd.pResource->GetResourceType() == GFxResource::RT_Image &&
pTempBindData->FillStyleImageWrap.Get(presData->BindIndex) == 0 &&
bd.pResource->GetResourceUse() == GFxResource::Use_Bitmap)
{
GPtr<GFxMovieDefImpl> pdefImpl = *pBindData->GetMovieDefImplAddRef();
GFxImageResource* pimageRes = (GFxImageResource*) (GFxResource*) bd.pResource;
pImagePacker->AddResource(presData, pimageRes);
}
// SetBindData AddRefs to the resource.
resourceBinding.SetBindData(presData->BindIndex, bd);
}
else
{
if (pBindData->BindingCanceled)
{
// Handle cancel. We may and up here if resource creating
// failed due to cancel (if it tired to access MovieDefImpl).
FinishBinding();
SetBindState(GFxMovieDefImpl::BS_Canceled | GetBindStateFlags());
#ifndef GFC_NO_THREADSUPPORT
GPtr<GFxLoadUpdateSync> psync = pBindData->GetBindUpdateSync();
pBindData = 0;
psync->NotifyLoadFinished();
#else
pBindData = 0;
#endif
return GFxMovieDefImpl::BS_Canceled;
}
// Set empty binding otherwise: this allows for indexing
// without problems.
resourceBinding.SetBindData(presData->BindIndex, bd);
// Fail the rest of loading/binding?
// Do we block on a certain frame?
// Who emits the error message?
}
}
}
// Make a list of fonts generated in this frame.
if (pframeData->FontCount)
{
GArray<GFxFontResource*> fonts;
GFxFontDataUseNode* pfontData = pframeData->pFontData;
UInt ifont;
for (ifont = 0; ifont < pframeData->FontCount;
ifont++, pfontData = pfontData->pNext)
{
GASSERT(pfontData != 0);
GFxResourceBindData rbd;
resourceBinding.GetResourceData(&rbd, pfontData->BindIndex);
if (rbd.pResource)
{
GASSERT(rbd.pResource->GetResourceType() == GFxResource::RT_Font);
GFxFontResource* pfont = (GFxFontResource*) rbd.pResource.GetPtr();
fonts.PushBack(pfont);
}
}
// Generate font bitmaps for the frame.
if (fonts.GetSize())
{
// NOTE: Some fonts may have already been rasterized if they were imported
// through '_glyphs' substitution, which can happen during CreateResource above.
if (pls->GetFontPackParams() != 0)
{
GFx_GenerateFontBitmaps(pls->GetFontPackParams(), fonts,
pls->GetBindStates()->pImageCreator,
pls->GetRenderConfig(),
pls->GetLog(),
&GlyphTextureIdGen,
pheap,
pls->IsThreadedLoading());
}
else
{
// Warn if there is no pack params and cache;
// this would cause glyphs to be rendered as shapes.
GFC_DEBUG_WARNING(!pls->GetFontCacheManager() ||
!pls->GetFontCacheManager()->IsDynamicCacheEnabled(),
"Both font packing and dynamic cache disabled - text will be vectorized");
}
}
}
// This frame done, advance and update state.
pBindData->BytesLoaded = pframeData->BytesLoaded;
GAtomicOps<UInt>::Store_Release(&pBindData->BindingFrame, pBindData->BindingFrame + 1);
if (pBindData->BindingFrame == 1)
{
// Frame 1 is special because clients can wait for it, so alter state.
SetBindState(GetBindState() | GFxMovieDefImpl::BSF_Frame1Loaded);
}
if (pBindData->BindingFrame == pDataDef->GetFrameCount())
{
// Update bytes loaded for the end of the file. This is necessary
// because we don't count the size of final tag 0 during loading.
pBindData->BytesLoaded = pDataDef->GetFileBytes();
if (pImagePacker)
{
struct ImagePackVisitor : public GFxMovieDef::ResourceVisitor
{
GFxImagePacker* pImagePacker;
ImagePackVisitor(GFxImagePacker* pImagePacker):pImagePacker(pImagePacker) { }
virtual void Visit(GFxMovieDef* pmovieDef, GFxResource* presource,
GFxResourceId rid, const char* pexportName)
{
GUNUSED2(rid, pmovieDef);
pImagePacker->AddImageFromResource((GFxImageResource*)presource, pexportName);
}
} visitor(pImagePacker);
GPtr<GFxMovieDefImpl> pdefImpl = *pBindData->GetMovieDefImplAddRef();
pdefImpl->VisitResources(&visitor, GFxMovieDef::ResVisit_Bitmaps);
}
FinishBinding();
// Update state and we are done.
SetBindState(GFxMovieDefImpl::BS_Finished |
GetBindStateFlags() | GFxMovieDefImpl::BSF_LastFrameLoaded);
}
GFxProgressHandler* ph = pls->GetProgressHandler();
if (ph)
{
ph->ProgressUpdate(GFxProgressHandler::Info(pBindData->GetDataDef()->pData->FileURL,
pBindData->BytesLoaded, pBindData->GetDataDef()->GetFileBytes(),
pBindData->BindingFrame, pBindData->GetDataDef()->GetFrameCount()));
}
return GetBindStateType();
}
void GFxMovieBindProcess::FinishBinding()
{
if (pImagePacker)
pImagePacker->Finish();
pBindData->ResourceBinding.Freeze();
}
GFxMovieDef::MemoryContext* GFxMovieDefImpl::CreateMemoryContext(const char* heapName,
const MemoryParams& memParams,
bool debugHeap)
{
UInt heapFlags = debugHeap ? GMemoryHeap::Heap_UserDebug : 0;
GMemoryHeap::HeapDesc desc = memParams.Desc;
desc.Flags |= heapFlags;
desc.Flags |= GMemoryHeap::Heap_FastTinyBlocks;
desc.Flags |= GMemoryHeap::Heap_ThreadUnsafe;
#ifdef GFX_AMP_SERVER_OR_RAGE_STATS // RAGE - protect stats builds as well as AMP
// disable Heap_ThreadUnsafe for AMP,
// since we get all memory reports from display thread
desc.Flags &= ~GMemoryHeap::Heap_ThreadUnsafe;
#endif
// RAGE - No dynamic granularity, since blocks bigger than 16k can get pretty hard to find. Keep them to a minimum
desc.Flags |= GMemoryHeap::Heap_FixedGranularity;
desc.HeapId = GHeapId_MovieView;
desc.Limit = GFxMovieRoot::MemoryContextImpl::HeapLimit::INITIAL_DYNAMIC_LIMIT; // 128K is the initial dynamic limit
GMemoryHeap* heap = GMemory::GetGlobalHeap()->CreateHeap(heapName, desc);
GFxMovieRoot::MemoryContextImpl* memContext = GHEAP_NEW(heap) GFxMovieRoot::MemoryContextImpl();
memContext->Heap = heap;
#ifndef GFC_NO_GC
memContext->ASGC = *GHEAP_NEW(heap) GASRefCountCollector();
memContext->ASGC->SetParams(memParams.FramesBetweenCollections, memParams.MaxCollectionRoots);
#endif
memContext->StringManager = *GHEAP_NEW(heap) GASStringManager(heap);
memContext->LimHandler.UserLevelLimit = memParams.Desc.Limit;
memContext->LimHandler.HeapLimitMultiplier = memParams.HeapLimitMultiplier;
heap->SetLimitHandler(&memContext->LimHandler);
heap->ReleaseOnFree(memContext);
return memContext;
}
// Create a playable GFxMovieSub instance from a def.
GFxMovieView* GFxMovieDefImpl::CreateInstance(const MemoryParams& memParams, bool initFirstFrame)
{
GString heapName("MovieView \"", G_GetShortFilename(GetFileURL()), "\"");
GFxMovieDef::MemoryContext* memContext = CreateMemoryContext(heapName.ToCStr(), memParams,
(GetLoadFlags() & GFxLoader::LoadDebugHeap) != 0);
GFxMovieView* movieView = CreateInstance(memContext, initFirstFrame);
memContext->Release(); // the MovieView owns the context now
return movieView;
}
GFxMovieView* GFxMovieDefImpl::CreateInstance(MemoryContext* memContext, bool initFirstFrame)
{
GFxMovieRoot* proot = GFxMovieRoot::Create(memContext);
if (proot == NULL)
{
return NULL;
}
// Note: Log is inherited dynamically from Def, so we don't set it here
GPtr<GFxSprite> prootMovie =
*GHEAP_NEW(proot->GetMovieHeap()) GFxSprite(GetDataDef(), this, proot,
NULL, GFxResourceId());
if (!prootMovie)
{
proot->Release();
return NULL;
}
// Assign level and _level0 name.
prootMovie->SetLevel(0);
proot->SetLevelMovie(0, prootMovie);
// Register aux AS classes defined in external libraries
proot->RegisterAuxASClasses();
if (proot->AdvanceStats)
{
proot->AdvanceStats->SetMovieDef(proot->GetMovieDef());
}
if (initFirstFrame)
{
proot->Advance(0.0f, 0);
}
// AddRef unnecessary, refCount == 1 on new
return proot;
}
// *** GFxMovieDefImpl::LoadData
GFxMovieDefImpl::BindTaskData::BindTaskData(GMemoryHeap* pheap,
GFxMovieDataDef *pdataDef,
GFxMovieDefImpl *pdefImpl,
UInt loadFlags, bool fullyLoaded)
: pHeap(pheap), pDataDef(pdataDef), pDefImpl_Unsafe(pdefImpl), ResourceBinding(pheap)
{
// Technically BindTaskData should not store a pointer to GFxMovieDefImpl,
// however, we just pass it to initialize regular ptr in GFxResourceBinding.
// This should not be a problem since it is only used where DefImpl reference
// is guaranteed to exist.
// (We should reset it to 0 on DefImpl death for consistency).
ResourceBinding.SetOwnerDefImpl(pdefImpl);
LoadFlags = loadFlags;
// Initialize binding progress state.
BindingCanceled = 0;
BindingFrame = 0;
BytesLoaded = 0;
BindState = BS_NotStarted;
#ifndef GFC_NO_THREADSUPPORT
pBindUpdate = *GNEW GFxLoadUpdateSync();
#endif
if (fullyLoaded)
{
BindingFrame = GetDataDef()->GetLoadingFrame();
BytesLoaded = GetDataDef()->GetFileBytes();
}
}
GFxMovieDefImpl::BindTaskData::~BindTaskData()
{
#ifndef GFC_NO_THREADSUPPORT
// Wait for the lock if some other objects is still accessing the object
GMutex::Locker lock(&pBindUpdate->GetMutex());
#endif
// Release all binding references before ImportSourceMovies is
// cleared. This is required because GFxSpriteDef references contain
// GASExecuteTag tags that must be destroyed before MovieDefImpl is
// released; thus all imported binding references must be cleared first.
// If this is not done tag destructors may crash if MovieDefImpl was
// released first.
ResourceBinding.Destroy();
}
void GFxMovieDefImpl::BindTaskData::OnMovieDefRelease()
{
// Clear pointers to our pDataDef.
ResourceBinding.SetOwnerDefImpl(0);
{ // lock scope.
GLock::Locker lock(&ImportSourceLock);
pDefImpl_Unsafe = 0;
}
// Set flag to cancel binding process.
if (GetBindStateType() <= BS_InProgress)
BindingCanceled = true;
// Signal the frame update mutex, since we could be waiting on it.
pDataDef->pData->NotifyFrameUpdated();
}
// Grabs OUR GFxMovieDefImpl through a lock; can return null.
GFxMovieDefImpl* GFxMovieDefImpl::BindTaskData::GetMovieDefImplAddRef()
{
GLock::Locker lock(&ImportSourceLock);
if (pDefImpl_Unsafe && pDefImpl_Unsafe->AddRef_NotZero())
return pDefImpl_Unsafe;
return 0;
}
// Bind State Updates / Waiting.
void GFxMovieDefImpl::BindTaskData::SetBindState(UInt newState)
{
#ifndef GFC_NO_THREADSUPPORT
GMutex::Locker lock(&pBindUpdate->GetMutex());
BindState = newState;
pBindUpdate->UpdateNotify();
#else
BindState = newState;
#endif
// if (GetBindStateType() == BS_Canceled)
// printf("GFxMovieDefImpl::BindTaskData::SetBindState, thread: %d\n", GetCurrentThreadId());
}
bool GFxMovieDefImpl::BindTaskData::WaitForBindStateFlags(UInt flags)
{
//printf("GFxMovieDefImpl::BindTaskData::WaitForBindStateFlags: %x, thread: %d\n", this, GetCurrentThreadId());
#ifndef GFC_NO_THREADSUPPORT
// Wait for for bind state flag or error. Return true for success,
// false if bind state was changed to error without setting the flags.
GMutex::Locker lock(&pBindUpdate->GetMutex());
while((GetBindStateType() < BS_Canceled) &&
!(GetBindState() & flags))
{
pBindUpdate->WaitForNotify();
}
#endif
return (GetBindState() & flags) != 0;
}
// Updates binding state Frame and BytesLoaded (called from image loading task).
void GFxMovieDefImpl::BindTaskData::UpdateBindingFrame(UInt frame, UInt32 bytesLoaded)
{
// Assign BytesLoaded first so that Advance gets them correctly for frame.
BytesLoaded = bytesLoaded;
GAtomicOps<UInt>::Store_Release(&BindingFrame, frame);
}
// Access import source movie based on import index with a lock.
GFxMovieDefImpl* GFxMovieDefImpl::BindTaskData::GetImportSourceMovie(UInt importIndex)
{
// Access ImportSourceMovies in a lock.
GLock::Locker loc(&ImportSourceLock);
if (importIndex >= ImportSourceMovies.GetSize())
return 0;
return ImportSourceMovies[importIndex];
}
// Adds a movie reference to ResourceImports array.
void GFxMovieDefImpl::BindTaskData::AddResourceImportMovie(GFxMovieDefImpl *pdefImpl)
{
ResourceImports.PushBack(pdefImpl);
}
// Grabs the stuff we want from the source pMovie.
void GFxMovieDefImpl::BindTaskData::ResolveImport(
GFxImportData* pimport, GFxMovieDefImpl* pdefImpl,
GFxLoadStates* pls, bool recursive)
{
bool imported = false;
// Go through all character ids and resolve their handles.
for (UInt i=0; i< pimport->Imports.GetSize(); i++)
{
GFxImportData::Symbol &symbol = pimport->Imports[i];
// Look for exports and substitute.
////////////////////////
// RAGE - Make sure we're not importing any fonts - they should all go through gfxfontlib.swf
#ifdef GFC_BUILD_DEBUG
if (symbol.SymbolName.ToCStr()[0] == '$')
{
GDebug::Message(GDebug::Message_Assert, "Import error: Importing a font symbol '%s' from '%s', all fonts should be imported through gfxfontlib.swf", symbol.SymbolName.ToCStr(), pimport->SourceUrl.ToCStr());
}
#endif
// Do the import.
GFxResourceBindData bindData;
if (!pdefImpl->GetExportedResource(&bindData, symbol.SymbolName))
{
if (pls->GetLog())
pls->GetLog()->LogError(
"Import error: GFxResource '%s' is not exported from movie '%s'\n",
symbol.SymbolName.ToCStr(), pimport->SourceUrl.ToCStr());
}
else
{
// Add it to binding for the character.
// Set imported flag so that we can AddRef to movieDef of an import,
// allowing resources such as fonts to work right.
imported = SetResourceBindData(GFxResourceId(symbol.CharacterId),
bindData, symbol.SymbolName.ToCStr());
}
}
// Hold a ref, to keep this source GFxMovieDef alive.
// TBD: Why only add if imported ??
if (imported)
{
if (!recursive)
{
GLock::Locker loc(&ImportSourceLock);
ImportSourceMovies.PushBack(pdefImpl);
}
}
// No font substitution for recursive imports.
if (recursive)
return;
// Name-based font import substitution:
// The Font EXPORT feature in Flash Studio ignores the embedded glyphs and instead,
// exports a fixed number of glyphs (243 or larger, depending on studio version),
// which makes it unstable for font imports.
// This name-based substitution can be used instead, replacing "Device Fonts" with
// identically named font from the shared font.swf file.
// To make exported fonts match up using export name we need glyphs to come
// from a different file. Font substitution occurs if source import file name
// has '_glyphs' in it.
bool forceFontSubstitution = 0;
GString lowerURL = pimport->SourceUrl.ToLower();
if (strstr(lowerURL.ToCStr(), "_glyphs") != 0)
{
forceFontSubstitution = 1;
// Save substitution so that it can be potentially checked.
pls->SubstituteFontMovieDefs.PushBack(pdefImpl);
}
/* Font substitution difficulties and how they are addressed:
Full font substitution can not take place here because some of the
FontUse entries (for future frames) may not yet be created. Similarly
there might be some thread considerations having to do with overwriting
fonts bound in previous frames.
Specifically, there are two substitution problems:
1) Previously bound fonts could have already been used by text field
instances; substituting them may be too late.
For now: Go through list, substitute anyway. This would not be
a problem if we could force all imports to take place before
font definitions. It is not clear if this is practical and can be
controlled in Flash studio to the extent we need - TBD (need
to research this).
For now, we can have text field cached texture glyph batches store
original font pointers and compare them to indexed font handle lookup
result. Text fields would then be updated in the event of change.
TBD - any thread issues with this?
2) Future fonts may not have FontUse array entries and thus need to
be substituted later on.
Solution: we add all forced substitutes sources to the
pls->SubstituteFontMovieDefs array. This array will need to be checked
when GFxFontResourceCreator::CreateResource is called to create
GFxFontResource from GFxFontData.
*/
// Iterate through fonts and resolve fonts with no glyphs from the def.
GFxFontDataUseNode* pfont = GetDataDef()->GetFirstFont();
GFxFontDataUseNode* psourceFont = pdefImpl->GetDataDef()->GetFirstFont();
for(; pfont != 0; pfont = pfont->pNext)
{
GFxFont* pfontData =pfont->pFontData;
if ((pfontData->GetGlyphShapeCount() == 0) || forceFontSubstitution)
{
// Search imports for the font with the same name which has glyphs.
GFxFontDataUseNode* psfont = psourceFont;
for(; psfont != 0; psfont = psfont->pNext)
{
GFxFont *psourceFontData = psfont->pFontData;
if (psourceFontData->GetGlyphShapeCount() > 0)
{
if (pfontData->MatchSubstituteFont(psourceFontData))
{
// With DefData separation, we do not need to ReplaceExport
// any more; updating the binding should be good enough.
// Set our binding.
// Note: Unlike us, the source is guaranteed to be fully loaded when
// this takes place, so we can just look up its binding.
GFxResourceBindData sourceBindData;
pdefImpl->pBindData->ResourceBinding.GetResourceData(
&sourceBindData,psfont->BindIndex);
if (sourceBindData.pResource)
{
ResourceBinding.SetBindData(pfont->BindIndex, sourceBindData);
}
break;
}
}
}
}
}
}
// Resolves an import of 'gfxfontlib.swf' through the GFxFontLib object.
bool GFxMovieDefImpl::BindTaskData::ResolveImportThroughFontLib(GFxImportData* pimport)
{
// Go through all character ids and resolve their handles.
for (UInt i=0; i< pimport->Imports.GetSize(); i++)
{
GFxImportData::Symbol &symbol = pimport->Imports[i];
GFxResourceBindData bindData;
bindData.pBinding = &ResourceBinding;
// Create a new font with FF_NotResolved flag set. This will instruct FontManager
// to search this font through FontLib and FontProvider
GPtr<GFxFont> font = *new GFxFontData(symbol.SymbolName.ToCStr(), 0);
font->SetNotResolvedFlag();
bindData.pResource= *new GFxFontResource(font, &ResourceBinding);
SetResourceBindData(GFxResourceId(symbol.CharacterId), bindData,
symbol.SymbolName.ToCStr());
}
// Add an empty import so that ImportSourceMovies still matches ImportData.
GLock::Locker loc(&ImportSourceLock);
ImportSourceMovies.PushBack(0);
return true;
}
// Internal helper for import updates.
bool GFxMovieDefImpl::BindTaskData::SetResourceBindData(
GFxResourceId rid, GFxResourceBindData& bindData,
const char* pimportSymbolName)
{
GFxResourceHandle rh;
if (GetDataDef()->GetResourceHandle(&rh, rid))
{
GASSERT(rh.IsIndex());
// Establish an association for BindIndex; note that
// ResourceBindData is not used here. For imported characters
// ResourceData elements will be empty.
ResourceBinding.SetBindData(rh.GetBindIndex(), bindData);
// Return imported flag so that the caller knows when to AddRef to the MovieDef
return true;
}
// The handle for import MUST exists in DataDef since it is created
// during the Read of import tags. If it doesn't, there is an error or
// perhaps we are out of memory.
GFC_DEBUG_WARNING1(1,
"Internal import bind error: bind handle not found for resource '%s'",
pimportSymbolName);
GUNUSED(pimportSymbolName); // For release build.
return false;
}
bool GFxMovieDefImpl::BindTaskData::SetResourceBindData(GFxResourceDataNode *presnode, GFxResource* pres)
{
GFxResourceBindData bd;
bd.pBinding = &ResourceBinding;
bd.pResource = pres;
ResourceBinding.SetBindData(presnode->BindIndex, bd);
return true;
}
// ***** GFxMovieDefBindStates
// GFxMovieDefBindStates is used as a part of the key object in GFxMovieDefImpl.
UPInt GFxMovieDefBindStates::GetHashCode() const
{
return
((UPInt)pFileOpener.GetPtr()) ^ (((UPInt)pFileOpener.GetPtr()) >> 7) ^
((UPInt)pURLBulider.GetPtr()) ^ (((UPInt)pURLBulider.GetPtr()) >> 7) ^
((UPInt)pImageCreator.GetPtr()) ^ (((UPInt)pImageCreator.GetPtr()) >> 7) ^
((UPInt)pImportVisitor.GetPtr()) ^ (((UPInt)pImportVisitor.GetPtr()) >> 7) ^
// ((UPInt)pImageVisitor.GetPtr()) ^ (((UPInt)pImageVisitor.GetPtr()) >> 7) ^
((UPInt)pGradientParams.GetPtr()) ^ (((UPInt)pGradientParams.GetPtr()) >> 7) ^
((UPInt)pFontPackParams.GetPtr()) ^ (((UPInt)pFontPackParams.GetPtr()) >> 7) ^
((UPInt)pPreprocessParams.GetPtr()) ^ (((UPInt)pPreprocessParams.GetPtr()) >> 7) ^
((UPInt)pFontCompactorParams.GetPtr()) ^ (((UPInt)pFontCompactorParams.GetPtr()) >> 7) ^
((UPInt)pImagePackParams.GetPtr()) ^ (((UPInt)pImagePackParams.GetPtr()) >> 7);
}
bool GFxMovieDefBindStates::operator == (GFxMovieDefBindStates& other) const
{
// For bind states to be identical all of the binding
// states must match.
return pFileOpener == other.pFileOpener &&
pURLBulider == other.pURLBulider &&
pImageCreator == other.pImageCreator &&
pImportVisitor == other.pImportVisitor &&
// pImageVisitor == other.pImageVisitor &&
pGradientParams == other.pGradientParams &&
pFontPackParams == other.pFontPackParams &&
pPreprocessParams == other.pPreprocessParams &&
pFontCompactorParams == other.pFontCompactorParams &&
pImagePackParams == other.pImagePackParams;
}
// ***** GFxMovieDefImpl Key
class GFxMovieDefImplKey : public GRefCountBase<GFxMovieDefImplKey, GStat_Default_Mem>
{
// MovieDefImpl key consists of DataDef and bind states pointers.
// pDataDef can not be inside of GFxMovieDefBindStates because
// the later is used in GFxLoadStates and thus referenced by the
// loading process.
GPtr<GFxMovieDataDef> pDataDef;
GPtr<GFxMovieDefBindStates> pBindStates;
public:
GFxMovieDefImplKey(GFxMovieDataDef* pdataDef, GFxMovieDefBindStates* pbindStates)
: pDataDef(pdataDef), pBindStates(pbindStates)
{
}
// Functionality necessary for this GFxMovieDefBindStates to be used
// as a key object for GFxMovieDefImpl.
UPInt GetHashCode() const
{
return ((UPInt)pDataDef.GetPtr()) ^ (((UPInt)pDataDef.GetPtr()) >> 7) ^
pBindStates->GetHashCode();
}
bool operator == (GFxMovieDefImplKey& other) const
{
return pDataDef == other.pDataDef && *pBindStates == *other.pBindStates;
}
bool operator != (GFxMovieDefImplKey& other) const { return !operator == (other); }
};
class GFxMovieDefImplKeyInterface : public GFxResourceKey::KeyInterface
{
public:
typedef GFxResourceKey::KeyHandle KeyHandle;
virtual void AddRef(KeyHandle hdata)
{
GASSERT(hdata); ((GFxMovieDefImplKey*) hdata)->AddRef();
}
virtual void Release(KeyHandle hdata)
{
GASSERT(hdata); ((GFxMovieDefImplKey*) hdata)->Release();
}
virtual GFxResourceKey::KeyType GetKeyType(KeyHandle hdata) const
{
GUNUSED(hdata); return GFxResourceKey::Key_Unique;
}
virtual UPInt GetHashCode(KeyHandle hdata) const
{
GASSERT(hdata);
return ((GFxMovieDefImplKey*) hdata)->GetHashCode();
}
virtual bool KeyEquals(KeyHandle hdata, const GFxResourceKey& other)
{
if (this != other.GetKeyInterface())
return 0;
return *((GFxMovieDefImplKey*) hdata) ==
*((GFxMovieDefImplKey*) other.GetKeyData());
}
};
static GFxMovieDefImplKeyInterface GFxMovieDefImplKeyInterface_Instance;
// Create a key for an SWF file corresponding to GFxMovieDef.
GFxResourceKey GFxMovieDefImpl::CreateMovieKey(GFxMovieDataDef *pdataDef,
GFxMovieDefBindStates* pbindStates)
{
GPtr<GFxMovieDefImplKey> pkey = *new GFxMovieDefImplKey(pdataDef, pbindStates);
return GFxResourceKey(&GFxMovieDefImplKeyInterface_Instance,
(GFxResourceKey::KeyHandle)pkey.GetPtr());
}
// Fill in the binding resource information together with its binding.
bool GFxMovieDefImpl::GetExportedResource(GFxResourceBindData *pdata, const GString& symbol, GFxMovieDefImpl* ignoreDef)
{
// Thread TBD: Should we block for availability depending on whether
// the source has finished loading?
GFxResourceHandle hres;
bool exportFound = 0;
{ // Lock Exports.
GFxMovieDataDef::LoadTaskData::ResourceLocker lock(GetDataDef()->pData);
exportFound = GetDataDef()->pData->Exports.GetCaseInsensitive(symbol, &hres);
}
if (exportFound)
{
// Determine the exact binding
if (hres.IsIndex())
{
pBindData->ResourceBinding.GetResourceData(pdata, hres.GetBindIndex());
}
else
{
pdata->pBinding = &pBindData->ResourceBinding;
pdata->pResource= hres.GetResource(&pBindData->ResourceBinding);
}
return (pdata->pResource.GetPtr() != 0);
}
else
{
// Nested import symbols are also visible in parent, although
// our own Exports table has precedence (checked for above).
GArray<GPtr<GFxMovieDefImpl>, GFxStatMD_Other_Mem> importsCopy;
{ // Access ImportSourceMovies in a lock.
GLock::Locker loc(&pBindData->ImportSourceLock);
importsCopy.Reserve(pBindData->ImportSourceMovies.GetSize());
for (UPInt i = 0; i< pBindData->ImportSourceMovies.GetSize(); i++)
{
GFxMovieDefImpl* pdef = pBindData->ImportSourceMovies[i];
if (pdef != ignoreDef)
importsCopy.PushBack(pdef);
}
}
// TBD: Any particular traversal order ?
for (UInt i = 0; i<importsCopy.GetSize(); i++)
{
GFxMovieDefImpl* pdef = importsCopy[i].GetPtr();
if (pdef && pdef->GetExportedResource(pdata, symbol))
return 1;
}
}
return 0;
}
bool GFxMovieDefImpl::DoesDirectlyImport(const GFxMovieDefImpl* import)
{
GLock::Locker loc(&pBindData->ImportSourceLock);
for (UPInt i = 0; i < pBindData->ImportSourceMovies.GetSize(); i++)
{
GFxMovieDefImpl* pdef = pBindData->ImportSourceMovies[i];
if (pdef == import)
return true;
}
return false;
}
const GString* GFxMovieDefImpl::GetNameOfExportedResource(GFxResourceId rid) const
{
GFxMovieDataDef::LoadTaskData::ResourceLocker lock(GetDataDef()->pData);
return GetDataDef()->pData->InvExports.Get(rid);
}
GFxResourceId GFxMovieDefImpl::GetExportedResourceIdByName(const GString& name) const
{
typedef GHashLH<GFxResourceId, GStringLH, GFixedSizeHash<GFxResourceId> > THASH;
GFxMovieDataDef::LoadTaskData::ResourceLocker lock(GetDataDef()->pData);
THASH::ConstIterator it = GetDataDef()->pData->InvExports.Begin();
for(; it != GetDataDef()->pData->InvExports.End(); ++it)
{
if (it->Second == name)
return it->First;
}
return GFxResourceId();
}
// *** Resource Lookup
// Obtains full character creation information, including GFxCharacterDef.
GFxCharacterCreateInfo GFxMovieDefImpl::GetCharacterCreateInfo(GFxResourceId rid)
{
GFxResourceHandle rh;
GFxCharacterCreateInfo ccinfo;
ccinfo.pCharDef = 0;
ccinfo.pBindDefImpl = 0;
if (GetDataDef()->GetResourceHandle(&rh, rid))
{
GFxResourceBinding* pbinding;
GFxResource* pres =
rh.GetResourceAndBinding(&pBindData->ResourceBinding, &pbinding);
if (pres)
{
if (pres->GetResourceType() & GFxResource::RT_CharacterDef_Bit)
{
ccinfo.pBindDefImpl = pbinding->GetOwnerDefImpl();
ccinfo.pCharDef = (GFxCharacterDef*) pres;
}
}
}
return ccinfo;
}
// Calls back the visitor for each GFxMovieSub that we
// import symbols from.
void GFxMovieDefImpl::VisitImportedMovies(GFxMovieDefImpl::ImportVisitor* visitor)
{
// Thread TBD: Ensure that all imports are loaded (?)
GFxImportData* pimportData = GetDataDef()->GetFirstImport();
if (pimportData)
{
// TBD: Visited may no longer be necessary in 2.0. However,
// we need to verify that it's not possible to produce to identical import
// file references in the Flash studio.
GFxStringHash<bool> visited;
while (pimportData)
{
GFxMovieDefImpl* pindexedMovie = 0;
{ // Access ImportSourceMovies in a lock.
GLock::Locker loc(&pBindData->ImportSourceLock);
if (pimportData->ImportIndex >= pBindData->ImportSourceMovies.GetSize())
{
// We may not want to allow this. The only time this could happen is
// if file is still being bound by another thread that has not caught
// up to this point, or binding error has occurred.
GASSERT(0);
break;
}
pindexedMovie = pBindData->ImportSourceMovies[pimportData->ImportIndex];
}
if (visited.FindCaseInsensitive(pimportData->SourceUrl) == visited.End())
{
// Call back the visitor.
if (pindexedMovie)
visitor->Visit(this, pindexedMovie,
pimportData->SourceUrl.ToCStr());
visited.SetCaseInsensitive(pimportData->SourceUrl, true);
}
pimportData = pimportData->pNext;
}
}
}
void GFxMovieDefImpl::VisitResources(ResourceVisitor* pvisitor, UInt visitMask)
{
if (visitMask & (GFxMovieDef::ResVisit_AllImages |
GFxMovieDef::ResVisit_Fonts |
GFxMovieDef::ResVisit_EditTextFields |
GFxMovieDef::ResVisit_Sounds |
GFxMovieDef::ResVisit_Sprite ) )
{
// Lock DataDef's Resources and Exports.
GFxMovieDataDef::LoadTaskData::ResourceLocker lock( GetDataDef()->pData );
GFxMovieDataDef::ResourceHash& resources = GetDataDef()->pData->Resources;
GFxMovieDataDef::ResourceHash::ConstIterator ihash = resources.Begin();
for(; ihash != resources.End(); ++ihash)
{
GFxResource *pres = ihash->Second.GetResource(&pBindData->ResourceBinding);
bool resourceMatch = 0;
if (pres)
{
GFxResource::ResourceUse use = pres->GetResourceUse();
switch(pres->GetResourceType())
{
case GFxResource::RT_Image:
{
if (use == GFxResource::Use_Bitmap)
{
if (visitMask & GFxMovieDef::ResVisit_Bitmaps)
resourceMatch = 1;
}
else if (use == GFxResource::Use_Gradient)
{
if (visitMask & GFxMovieDef::ResVisit_GradientImages)
resourceMatch = 1;
}
}
break;
case GFxResource::RT_Font:
if (visitMask & GFxMovieDef::ResVisit_Fonts)
resourceMatch = 1;
break;
case GFxResource::RT_EditTextDef:
if (visitMask & GFxMovieDef::ResVisit_EditTextFields)
resourceMatch = 1;
break;
case GFxResource::RT_SoundSample:
if (visitMask & GFxMovieDef::ResVisit_Sounds)
resourceMatch = 1;
break;
case GFxResource::RT_SpriteDef:
if (visitMask & GFxMovieDef::ResVisit_Sprite)
resourceMatch = 1;
break;
default: break;
}
}
if (resourceMatch)
{
// Determine export name by doing a search.
const char* pexportName = 0;
GFxStringHashLH<GFxResourceHandle> &exports = GetDataDef()->pData->Exports;
GFxStringHashLH<GFxResourceHandle>::Iterator iexport = exports.Begin();
while (iexport != exports.End())
{
if (iexport->Second.Equals(ihash->Second))
{
pexportName = iexport->First.ToCStr();
break;
}
++iexport;
}
pvisitor->Visit(this, pres, ihash->First, pexportName);
}
}
}
// Check to see if we need to traverse nested files.
if (visitMask & GFxMovieDef::ResVisit_NestedMovies)
{
// visit imported movies
GArray<GPtr<GFxMovieDefImpl>, GFxStatMD_Other_Mem> importsCopy;
{ // Access ImportSourceMovies in a lock.
GLock::Locker loc(&pBindData->ImportSourceLock);
//importsCopy = pBindData->ImportSourceMovies;
importsCopy.Reserve(pBindData->ImportSourceMovies.GetSize());
for (UPInt i = 0; i< pBindData->ImportSourceMovies.GetSize(); i++)
importsCopy.PushBack(pBindData->ImportSourceMovies[i]);
}
for(UPInt i = 0, n = importsCopy.GetSize(); i < n; ++i)
{
if (importsCopy[i])
importsCopy[i]->VisitResources(pvisitor, visitMask);
}
}
}
GFxResource* GFxMovieDefImpl::GetResource(const char *pexportName) const
{
if (!pexportName)
return 0;
// Find the export string
GString str(pexportName);
GFxMovieDataDef::LoadTaskData::ResourceLocker lock( GetDataDef()->pData );
GFxStringHashLH<GFxResourceHandle>& exports = GetDataDef()->pData->Exports;
GFxStringHashLH<GFxResourceHandle>::ConstIterator iexport = exports.FindCaseInsensitive(str);
if ( iexport == exports.End())
return 0;
// TBD: Thread issues?
// Check if the resource has been resolved yet. Not quite right since
// pointer-based handles are possible.
//if (iexport->Second.GetBindIndex() >= ResourceBinding.GetResourceCount())
// return 0;
GFxResource *pres = iexport->Second.GetResource(&pBindData->ResourceBinding);
return pres;
}
// Locate a font resource by name and style.
// It's ok to return GFxFontResource* without the binding because pBinding
// is embedded into font resource allowing imports to be handled properly.
GFxFontResource* GFxMovieDefImpl::GetFontResource(const char* pfontName, UInt styleFlags, SearchInfo* psearchInfo)
{
GFxMovieDataDef* pdataDef = GetDataDef();
// TBD: We will need to do something about threading here, since it is legit
// to call GetFontResource while loading still hasn't completed.
GFxFontDataUseNode *pfont = pdataDef->GetFirstFont();
for(; pfont != 0; pfont = pfont->pNext)
{
if (pfont->pFontData->MatchFont(pfontName, styleFlags))
{
// TBD: Wait for resource binding?
GFxResourceBindData rbd;
pBindData->ResourceBinding.GetResourceData(&rbd, pfont->BindIndex);
GFC_DEBUG_WARNING1(!rbd.pResource, "rbd.pResource = 0 for font '%s' ", pfontName);
if (rbd.pResource)
{
GASSERT(rbd.pResource->GetResourceType() == GFxResource::RT_Font);
GFxFontResource* pfont = (GFxFontResource*)rbd.pResource.GetPtr();
if (psearchInfo)
{
if (!pfont->GetFont()->IsResolved())
psearchInfo->Status = SearchInfo::FoundInResourcesNoGlyphs;
else if ((styleFlags & GFxFont::FF_BoldItalic) && !pfont->GetFont()->HasVectorOrRasterGlyphs())
psearchInfo->Status = SearchInfo::FoundInResourcesNeedFaux;
else
psearchInfo->Status = SearchInfo::FoundInResources;
}
return pfont;
}
}
}
// Import names also look like font names for lookup purposes.
// In particular, we need to do this to support imported shared font,
// where export names are used during lookup.
// We should look not only for import names but also for real font names.
// Imported font should be locatable either by import name (like $TitleFont)
// or by the real font name (like Times New Roman).
GFxImportData* pimport = pdataDef->GetFirstImport();
for(; pimport != 0; pimport = pimport->pNext)
{
for (UInt j = 0; j<pimport->Imports.GetSize(); j++)
{
// Locate the font and match style flags.
// We will also have to report the new name so that it can be
// detected in the font manager.
if (psearchInfo)
psearchInfo->ImportSearchUrls.Set(pimport->SourceUrl);
GFxResourceHandle rh;
if (pdataDef->GetResourceHandle(&rh, GFxResourceId(pimport->Imports[j].CharacterId)))
{
GFxResource* pres = rh.GetResource(&pBindData->ResourceBinding);
// The resource must be a font.
if (pres && (pres->GetResourceType() == GFxResource::RT_Font))
{
GFxFontResource* pfontRes = (GFxFontResource*)pres;
if (pfontRes->GetFont()->MatchFontFlags(styleFlags))
{
// Compare the import font name and the real font name.
if (!pimport->Imports[j].SymbolName.CompareNoCase(pfontName) ||
!GString::CompareNoCase(pfontRes->GetName(), pfontName))
{
// We found it!
// Fill in name, since font will not have its export name.
if (psearchInfo)
{
static const char fontlibKey[] = GFX_FONTLIB_IMPORT_KEY;
const UPInt fontlibKeySize = sizeof(fontlibKey) - 1;
if ((pimport->SourceUrl.GetSize() >= fontlibKeySize) &&
(GString::CompareNoCase(pimport->SourceUrl.ToCStr() +
(pimport->SourceUrl.GetSize() - fontlibKeySize), fontlibKey) == 0))
psearchInfo->Status = SearchInfo::FoundInImportsFontLib;
else
psearchInfo->Status = SearchInfo::FoundInImports;
psearchInfo->ImportFoundUrl = pimport->SourceUrl;
}
return pfontRes;
}
}
}
}
}
}
// Look for the names of EXPORTED fonts too (!AB)
GFxResource* pres = GetResource(pfontName);
if (pres && (pres->GetResourceType() == GFxResource::RT_Font))
{
GFxFontResource* pfontRes = (GFxFontResource*)pres;
if (pfontRes->GetFont()->MatchFontFlags(styleFlags))
{
// We found it!
// Fill in name, since font will not have its export name.
if (psearchInfo)
psearchInfo->Status = SearchInfo::FoundInExports;
return pfontRes;
}
}
if (psearchInfo)
psearchInfo->Status = SearchInfo::NotFound;
return 0;
}
// *** GASExecuteTag - debug counters
#ifdef GFC_BUILD_DEBUG
class ASClassCounter
{
public:
GAtomicInt<UInt> Count;
ASClassCounter() { Count = 0; }
// If this assertion is hit, it means that some allocated
// GASExecuteTag(s) weren't properly cleaned up.
~ASClassCounter() {}//{ GASSERT(Count == 0); }
};
ASClassCounter AS_Counter_Instance;
GASExecuteTag::GASExecuteTag()
{
AS_Counter_Instance.Count++;
//Sentinel = 0xE465F1A9;
}
GASExecuteTag::~GASExecuteTag()
{
//GASSERT(Sentinel == 0xE465F1A9);
//Sentinel = 0;
AS_Counter_Instance.Count--;
}
#endif
// ***** GFxInitImportActions - created for import tags.
// Queues up logic to execute InitClip actions from the import,
// using Highest priority by default.
void GFxInitImportActions::Execute(GFxSprite* m)
{
// For regular execute our context is the same as of target sprite,
// but it gets overriden if called from GFxSprite::ExecuteImportedInitActions.
ExecuteInContext(m, m->GetResourceMovieDef(), 0);
}
// InitImportActions that come from imported files need to be executed
// in the MovieDefImpl binding context (otherwise we would index parent's
// source movies incorrectly, resulting in recursive loop).
void GFxInitImportActions::ExecuteInContext(GFxSprite* m, GFxMovieDefImpl *pbindDef, bool recursiveCheck)
{
GFxMovieDefImpl* psourceImportMovie =
pbindDef->pBindData->GetImportSourceMovie(ImportIndex);
if (psourceImportMovie &&
psourceImportMovie->GetDataDef()->HasInitActions())
{
// It is possible that SWF files will import themselves or other files recursively.
// Check for that and don't run sprite's ResourceMovieDef's actions again.
if (recursiveCheck && (psourceImportMovie == m->GetResourceMovieDef()))
return;
m->ExecuteImportedInitActions(psourceImportMovie);
}
}