Index: ps/trunk/source/ps/Profile.cpp =================================================================== --- ps/trunk/source/ps/Profile.cpp (revision 5179) +++ ps/trunk/source/ps/Profile.cpp (revision 5180) @@ -1,511 +1,554 @@ /** * ========================================================================= * File : Profile.cpp * Project : Pyrogeneses * Description : GPG3-style hierarchical profiler * ========================================================================= */ #include "precompiled.h" #include "Profile.h" #include "ProfileViewer.h" #include "lib/timer.h" +#if GCC_VERSION && !defined(NDEBUG) +# define GLIBC_MALLOC_HOOK +# include +# include "ThreadUtil.h" +#endif + /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileNodeTable /** * Class CProfileNodeTable: Implement ProfileViewer's AbstractProfileTable * interface in order to display profiling data in-game. */ class CProfileNodeTable : public AbstractProfileTable { public: CProfileNodeTable(CProfileNode* n); virtual ~CProfileNodeTable(); // Implementation of AbstractProfileTable interface virtual CStr GetName(); virtual CStr GetTitle(); virtual size_t GetNumberRows(); virtual const std::vector& GetColumns(); virtual CStr GetCellText(size_t row, size_t col); virtual AbstractProfileTable* GetChild(size_t row); virtual bool IsHighlightRow(size_t row); private: /** * struct ColumnDescription: The only purpose of this helper structure * is to provide the global constructor that sets up the column * description. */ struct ColumnDescription { std::vector columns; ColumnDescription() { columns.push_back(ProfileColumn("Name", 230)); columns.push_back(ProfileColumn("calls/frame", 100)); columns.push_back(ProfileColumn("msec/frame", 100)); columns.push_back(ProfileColumn("%/frame", 100)); columns.push_back(ProfileColumn("%/parent", 100)); columns.push_back(ProfileColumn("mem allocs", 100)); } }; /// The node represented by this table CProfileNode* node; /// Columns description (shared by all instances) static ColumnDescription columnDescription; }; CProfileNodeTable::ColumnDescription CProfileNodeTable::columnDescription; // Constructor/Destructor CProfileNodeTable::CProfileNodeTable(CProfileNode* n) { node = n; } CProfileNodeTable::~CProfileNodeTable() { } // Short name (= name of profile node) CStr CProfileNodeTable::GetName() { return node->GetName(); } // Title (= explanatory text plus time totals) CStr CProfileNodeTable::GetTitle() { char buf[512]; snprintf(buf, sizeof(buf), "Profiling Information for: %s (Time in node: %.3f msec/frame)", node->GetName(), node->GetFrameTime() * 1000.0f ); buf[sizeof(buf)-1] = '\0'; return buf; } // Total number of children size_t CProfileNodeTable::GetNumberRows() { return node->GetChildren()->size() + node->GetScriptChildren()->size() + 1; } // Column description const std::vector& CProfileNodeTable::GetColumns() { return columnDescription.columns; } // Retrieve cell text CStr CProfileNodeTable::GetCellText(size_t row, size_t col) { CProfileNode* child; size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); char buf[256] = "?"; if (row < nrchildren) child = (*node->GetChildren())[row]; else if (row < nrchildren + nrscriptchildren) child = (*node->GetScriptChildren())[row - nrchildren]; else if (row > nrchildren + nrscriptchildren) return "!bad row!"; else { // "unlogged" row if (col == 0) return "unlogged"; else if (col == 1) return ""; float unlogged = node->GetFrameTime(); long unlogged_mallocs = node->GetFrameMallocs(); CProfileNode::const_profile_iterator it; for (it = node->GetChildren()->begin(); it != node->GetChildren()->end(); ++it) { unlogged -= (*it)->GetFrameTime(); unlogged_mallocs -= (*it)->GetFrameMallocs(); } for (it = node->GetScriptChildren()->begin(); it != node->GetScriptChildren()->end(); ++it) { unlogged -= (*it)->GetFrameTime(); unlogged_mallocs -= (*it)->GetFrameMallocs(); } if (col == 2) snprintf(buf, sizeof(buf), "%.3f", unlogged * 1000.0f); else if (col == 3) snprintf(buf, sizeof(buf), "%.1f", unlogged / g_Profiler.GetRoot()->GetFrameTime()); else if (col == 4) snprintf(buf, sizeof(buf), "%.1f", unlogged * 100.0f / g_Profiler.GetRoot()->GetFrameTime()); else if (col == 5) snprintf(buf, sizeof(buf), "%ld", unlogged_mallocs); buf[sizeof(buf)-1] = '\0'; return CStr(buf); } switch(col) { default: case 0: return child->GetName(); case 1: #ifdef PROFILE_AMORTIZE snprintf(buf, sizeof(buf), "%.3f", child->GetFrameCalls()); #else snprintf(buf, sizeof(buf), "%d", child->GetFrameCalls()); #endif break; case 2: snprintf(buf, sizeof(buf), "%.3f", child->GetFrameTime() * 1000.0f); break; case 3: snprintf(buf, sizeof(buf), "%.1f", child->GetFrameTime() * 100.0 / g_Profiler.GetRoot()->GetFrameTime()); break; case 4: snprintf(buf, sizeof(buf), "%.1f", child->GetFrameTime() * 100.0 / node->GetFrameTime()); break; case 5: snprintf(buf, sizeof(buf), "%ld", child->GetFrameMallocs()); break; } buf[sizeof(buf)-1] = '\0'; return CStr(buf); } // Return a pointer to the child table if the child node is expandable AbstractProfileTable* CProfileNodeTable::GetChild(size_t row) { CProfileNode* child; size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); if (row < nrchildren) child = (*node->GetChildren())[row]; else if (row < nrchildren + nrscriptchildren) child = (*node->GetScriptChildren())[row - nrchildren]; else return 0; if (child->CanExpand()) return child->display_table; return 0; } // Highlight all script nodes bool CProfileNodeTable::IsHighlightRow(size_t row) { size_t nrchildren = node->GetChildren()->size(); size_t nrscriptchildren = node->GetScriptChildren()->size(); return (row >= nrchildren && row < (nrchildren + nrscriptchildren)); } /////////////////////////////////////////////////////////////////////////////////////////////// // CProfileNode implementation // Note: As with the GPG profiler, name is assumed to be a pointer to a constant string; only pointer equality is checked. CProfileNode::CProfileNode( const char* _name, CProfileNode* _parent ) { name = _name; recursion = 0; Reset(); parent = _parent; display_table = new CProfileNodeTable(this); Reset(); } CProfileNode::~CProfileNode() { profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) delete( *it ); for( it = script_children.begin(); it != script_children.end(); it++ ) delete( *it ); delete display_table; } const CProfileNode* CProfileNode::GetChild( const char* childName ) const { const_profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) if( (*it)->name == childName ) return( *it ); return( NULL ); } const CProfileNode* CProfileNode::GetScriptChild( const char* childName ) const { const_profile_iterator it; for( it = script_children.begin(); it != script_children.end(); it++ ) if( (*it)->name == childName ) return( *it ); return( NULL ); } CProfileNode* CProfileNode::GetChild( const char* childName ) { profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) if( (*it)->name == childName ) return( *it ); CProfileNode* newNode = new CProfileNode( childName, this ); children.push_back( newNode ); return( newNode ); } CProfileNode* CProfileNode::GetScriptChild( const char* childName ) { profile_iterator it; for( it = script_children.begin(); it != script_children.end(); it++ ) if( (*it)->name == childName ) return( *it ); CProfileNode* newNode = new CProfileNode( childName, this ); script_children.push_back( newNode ); return( newNode ); } bool CProfileNode::CanExpand() { return( !( children.empty() && script_children.empty() ) ); } void CProfileNode::Reset() { calls_total = 0; calls_frame_current = 0; #ifdef PROFILE_AMORTIZE int i; for( i = 0; i < PROFILE_AMORTIZE_FRAMES; i++ ) { calls_frame_buffer[i] = 0; time_frame_buffer[i] = 0.0; } calls_frame_last = calls_frame_buffer; calls_frame_amortized = 0.0f; #else calls_frame_last = 0; #endif time_total = 0.0; time_frame_current = 0.0; #ifdef PROFILE_AMORTIZE time_frame_last = time_frame_buffer; time_frame_amortized = 0.0; #else time_frame_last = 0.0; #endif mallocs_total = 0; mallocs_frame_current = 0; mallocs_frame_last = 0; profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) (*it)->Reset(); for( it = script_children.begin(); it != script_children.end(); it++ ) (*it)->Reset(); } void CProfileNode::Frame() { calls_total += calls_frame_current; time_total += time_frame_current; mallocs_total += mallocs_frame_current; #ifdef PROFILE_AMORTIZE calls_frame_amortized -= *calls_frame_last; *calls_frame_last = calls_frame_current; calls_frame_amortized += calls_frame_current; time_frame_amortized -= *time_frame_last; *time_frame_last = time_frame_current; time_frame_amortized += time_frame_current; if( ++calls_frame_last == ( calls_frame_buffer + PROFILE_AMORTIZE_FRAMES ) ) calls_frame_last = calls_frame_buffer; if( ++time_frame_last == ( time_frame_buffer + PROFILE_AMORTIZE_FRAMES ) ) time_frame_last = time_frame_buffer; #else calls_frame_last = calls_frame_current; time_frame_last = time_frame_current; #endif mallocs_frame_last = mallocs_frame_current; calls_frame_current = 0; time_frame_current = 0.0; mallocs_frame_current = 0; profile_iterator it; for( it = children.begin(); it != children.end(); it++ ) (*it)->Frame(); for( it = script_children.begin(); it != script_children.end(); it++ ) (*it)->Frame(); } +// TODO: these should probably only count allocations that occur in the thread being profiled +#if HAVE_VC_DEBUG_ALLOC +static long memory_alloc_bias = 0; // so we can subtract the allocations caused by this function static long get_memory_alloc_count() { -#if HAVE_VC_DEBUG_ALLOC // TODO: it's probably better to use _CrtSetAllocHook to increment a // user-visible counter. (I didn't know that existed when I wrote this.) // Find the number of allocations that have ever occurred, by doing a dummy // allocation and checking its request number - static long bias = 0; // so we can subtract the allocations caused by this function void* p = malloc(1); long requestNumber = 0; int ok = _CrtIsMemoryBlock(p, 1, &requestNumber, NULL, NULL); UNUSED2(ok); free(p); - ++bias; - return requestNumber - bias; + cpu_AtomicAdd(&memory_alloc_bias, 1); + return requestNumber - memory_alloc_bias; +} +#elif defined(GLIBC_MALLOC_HOOK) +// Set up malloc hooks to count allocations - see +// http://www.gnu.org/software/libc/manual/html_node/Hooks-for-Malloc.html +static int malloc_count = 0; +static void *(*old_malloc_hook) (size_t, const void*); +static CMutex malloc_mutex; +static void *malloc_hook(size_t size, const void* caller) +{ + // TODO: this is totally not nicely thread-safe - glibc's hook system seems to + // not make threading easy, and I don't want to think too hard, so this is + // just all put inside a lock. + CScopeLock lock(malloc_mutex); + + ++malloc_count; + __malloc_hook = old_malloc_hook; + void* result = malloc(size); + old_malloc_hook = __malloc_hook; + __malloc_hook = malloc_hook; + return result; +} +static void malloc_initialize_hook() +{ + CScopeLock lock(malloc_mutex); + + old_malloc_hook = __malloc_hook; + __malloc_hook = malloc_hook; +} +/* +It would be nice to do: + __attribute__ ((visibility ("default"))) void (*__malloc_initialize_hook)() = malloc_initialize_hook; +except that doesn't seem to work in practice, since something (?) resets the +hook to NULL some time while loading the game, after we've set it here - so +we just call malloc_initialize_hook once inside CProfileManager::Frame instead +and hope nobody deletes our hook after that. +*/ +static long get_memory_alloc_count() +{ + return malloc_count; +} #else - // TODO: support other compilers if it's easy. +static long get_memory_alloc_count() +{ // TODO: don't show this column of data when we don't have sensible values // to display. return 0; -#endif } +#endif void CProfileNode::Call() { calls_frame_current++; if( recursion++ == 0 ) { start = get_time(); start_mallocs = get_memory_alloc_count(); } } bool CProfileNode::Return() { if( !parent ) return( false ); if( ( --recursion == 0 ) && ( calls_frame_current != 0 ) ) { time_frame_current += ( get_time() - start ); mallocs_frame_current += ( get_memory_alloc_count() - start_mallocs ); } return( recursion == 0 ); } void CProfileNode::ScriptingInit() { AddProperty( L"name", (IJSObject::GetFn)&CProfileNode::JS_GetName ); /* AddReadOnlyClassProperty( L"callsTotal", &CProfileNode::calls_total ); AddReadOnlyClassProperty( L"callsPerFrame", &CProfileNode::calls_frame_last ); AddReadOnlyClassProperty( L"timeTotal", &CProfileNode::time_total ); AddReadOnlyClassProperty( L"timePerFrame", &CProfileNode::time_frame_last ); */ CJSObject::ScriptingInit( "ProfilerNode" ); } CProfileManager::CProfileManager() { root = new CProfileNode( "root", NULL ); current = root; frame_start = 0.0; + frame_start_mallocs = 0; g_ProfileViewer.AddRootTable(root->display_table); } CProfileManager::~CProfileManager() { std::map::iterator it; for( it = m_internedStrings.begin(); it != m_internedStrings.end(); it++ ) delete[]( it->second ); delete( root ); } void CProfileManager::Start( const char* name ) { if( name != current->GetName() ) current = current->GetChild( name ); current->Call(); } void CProfileManager::StartScript( const char* name ) { if( name != current->GetName() ) current = current->GetScriptChild( name ); current->Call(); } const char* CProfileManager::InternString( const CStr8& intern ) { std::map::iterator it = m_internedStrings.find( intern ); if( it != m_internedStrings.end() ) return( it->second ); size_t length = intern.length(); char* data = new char[length + 1]; strcpy( data, intern.c_str() ); data[length] = 0; m_internedStrings.insert( std::pair( intern, data ) ); return( data ); } void CProfileManager::Stop() { if( current->Return() ) current = current->GetParent(); } void CProfileManager::Reset() { root->Reset(); - start = get_time(); - frame_start = get_time(); + start = frame_start = get_time(); + start_mallocs = frame_start_mallocs = get_memory_alloc_count(); } void CProfileManager::Frame() { +#ifdef GLIBC_MALLOC_HOOK + ONCE(malloc_initialize_hook()); +#endif + root->time_frame_current = ( get_time() - frame_start ); + root->mallocs_frame_current = ( get_memory_alloc_count() - frame_start_mallocs ); root->Frame(); frame_start = get_time(); + frame_start_mallocs = get_memory_alloc_count(); } void CProfileManager::StructuralReset() { delete( root ); root = new CProfileNode( "root", NULL ); current = root; g_ProfileViewer.AddRootTable(root->display_table); } - -double CProfileManager::GetTime() -{ - return( get_time() - start ); -} - -double CProfileManager::GetFrameTime() -{ - return( get_time() - frame_start ); -} - - Index: ps/trunk/source/ps/Profile.h =================================================================== --- ps/trunk/source/ps/Profile.h (revision 5179) +++ ps/trunk/source/ps/Profile.h (revision 5180) @@ -1,168 +1,168 @@ /** * ========================================================================= * File : Profile.h * Project : Pyrogenesis * Description : GPG3-style hierarchical profiler * ========================================================================= */ #ifndef INCLUDED_PROFILE #define INCLUDED_PROFILE #include #include "Singleton.h" #include "scripting/ScriptableObject.h" #define PROFILE_AMORTIZE #define PROFILE_AMORTIZE_FRAMES 50 class CProfileManager; class CProfileNodeTable; class CProfileNode : public CJSObject { friend class CProfileManager; friend class CProfileNodeTable; const char* name; int calls_total; int calls_frame_current; #ifdef PROFILE_AMORTIZE int calls_frame_buffer[PROFILE_AMORTIZE_FRAMES]; int* calls_frame_last; float calls_frame_amortized; #else int calls_frame_last; #endif double time_total; double time_frame_current; #ifdef PROFILE_AMORTIZE double time_frame_buffer[PROFILE_AMORTIZE_FRAMES]; double* time_frame_last; double time_frame_amortized; #else double time_frame_last; #endif long mallocs_total; long mallocs_frame_current; long mallocs_frame_last; double start; long start_mallocs; int recursion; CProfileNode* parent; std::vector children; std::vector script_children; CProfileNodeTable* display_table; public: typedef std::vector::iterator profile_iterator; typedef std::vector::const_iterator const_profile_iterator; CProfileNode( const char* name, CProfileNode* parent ); ~CProfileNode(); const char* GetName() const { return( name ); } int GetCalls() const { return( calls_total ); } double GetTime() const { return( time_total ); } #ifdef PROFILE_AMORTIZE float GetFrameCalls() const { return( calls_frame_amortized / PROFILE_AMORTIZE_FRAMES ); } double GetFrameTime() const { return( time_frame_amortized / PROFILE_AMORTIZE_FRAMES ); } #else int GetFrameCalls() const { return( calls_frame_last ); } double GetFrameTime() const { return( time_frame_last ); } #endif long GetFrameMallocs() const { return( mallocs_frame_last ); } const CProfileNode* GetChild( const char* name ) const; const CProfileNode* GetScriptChild( const char* name ) const; const std::vector* GetChildren() const { return( &children ); } const std::vector* GetScriptChildren() const { return( &script_children ); } bool CanExpand(); CProfileNode* GetChild( const char* name ); CProfileNode* GetScriptChild( const char* name ); CProfileNode* GetParent() const { return( parent ); } // Resets timing information for this node and all its children void Reset(); // Resets frame timings for this node and all its children void Frame(); // Enters the node void Call(); // Leaves the node. Returns true if the node has actually been left bool Return(); // Javascript stuff... static void ScriptingInit(); jsval JS_GetName(JSContext*) { return( ToJSVal( CStrW( name ) ) ); } }; class CProfileManager : public Singleton { CProfileNode* root; CProfileNode* current; double start; double frame_start; + long start_mallocs; + long frame_start_mallocs; std::map m_internedStrings; public: CProfileManager(); ~CProfileManager(); // Begins timing for a named subsection void Start( const char* name ); void StartScript( const char* name ); // Ends timing for the current subsection void Stop(); // Resets all timing information void Reset(); // Resets frame timing information void Frame(); // Resets absolutely everything void StructuralReset(); const char* InternString( const CStr8& intern ); inline const CProfileNode* GetCurrent() { return( current ); } inline const CProfileNode* GetRoot() { return( root ); } - double GetTime(); - double GetFrameTime(); }; #define g_Profiler CProfileManager::GetSingleton() class CProfileSample { static std::map evMap; public: CProfileSample( const char* name ) { if (CProfileManager::IsInitialised()) g_Profiler.Start( name ); } ~CProfileSample() { if (CProfileManager::IsInitialised()) g_Profiler.Stop(); } }; // Put a PROFILE( xyz ) block at the start of all code to be profiled. // Profile blocks last until the end of the containing scope. #define PROFILE( name ) CProfileSample __profile( name ) // Cheat a bit to make things slightly easier on the user #define PROFILE_START( name ) { CProfileSample __profile( name ) #define PROFILE_END( name ) } #endif // INCLUDED_PROFILE Index: ps/trunk/source/gui/CGUI.cpp =================================================================== --- ps/trunk/source/gui/CGUI.cpp (revision 5179) +++ ps/trunk/source/gui/CGUI.cpp (revision 5180) @@ -1,1894 +1,1897 @@ /* CGUI */ #include "precompiled.h" #include #include #include "lib/res/graphics/unifont.h" #include "GUI.h" // Types - when including them into the engine. #include "CButton.h" #include "CImage.h" #include "CText.h" #include "CCheckBox.h" #include "CRadioButton.h" #include "CInput.h" #include "CList.h" #include "CDropDown.h" #include "CProgressBar.h" #include "CTooltip.h" #include "MiniMap.h" #include "ps/XML/Xeromyces.h" #include "ps/Font.h" #include "ps/Pyrogenesis.h" #include "lib/input.h" #include "lib/bits.h" // TODO Gee: Whatever include CRect/CPos/CSize #include "ps/Overlay.h" +#include "ps/Profile.h" #include "scripting/ScriptingHost.h" #include "ps/Hotkey.h" #include "ps/Globals.h" #include "lib/timer.h" #include "lib/sysdep/sysdep.h" // namespaces used using namespace std; const double SELECT_DBLCLICK_RATE = 0.5; #include "ps/CLogger.h" #define LOG_CATEGORY "gui" // Class for global JavaScript object JSClass GUIClass = { "GUIClass", 0, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_PropertyStub, JS_EnumerateStub, JS_ResolveStub, JS_ConvertStub, JS_FinalizeStub, }; //------------------------------------------------------------------- // called from main loop when (input) events are received. // event is passed to other handlers if false is returned. // trampoline: we don't want to make the implementation (in CGUI) static //------------------------------------------------------------------- InReaction gui_handler(const SDL_Event_* ev) { + PROFILE(" GUI event handler "); return g_GUI.HandleEvent(ev); } InReaction CGUI::HandleEvent(const SDL_Event_* ev) { InReaction ret = IN_PASS; if (ev->ev.type == SDL_GUIHOTKEYPRESS) { const CStr& objectName = *(CStr*) ev->ev.user.data1; IGUIObject* object = FindObjectByName(objectName); if (! object) { LOG(ERROR, LOG_CATEGORY, "Cannot find hotkeyed object '%s'", objectName.c_str()); } else { object->HandleMessage( SGUIMessage( GUIM_PRESSED ) ); object->ScriptEvent("press"); } } else if (ev->ev.type == SDL_MOUSEMOTION) { // Yes the mouse position is stored as float to avoid // constant conversions when operating in a // float-based environment. m_MousePos = CPos((float)ev->ev.motion.x, (float)ev->ev.motion.y); GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_MOUSE_MOTION)); } // Update m_MouseButtons. (BUTTONUP is handled later.) else if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons |= BIT(ev->ev.button.button); break; default: break; } } // JW: (pre|post)process omitted; what're they for? why would we need any special button_released handling? // Only one object can be hovered IGUIObject *pNearest = NULL; // TODO Gee: (2004-09-08) Big TODO, don't do the below if the SDL_Event is something like a keypress! try { + PROFILE( "mouse events" ); // TODO Gee: Optimizations needed! // these two recursive function are quite overhead heavy. // pNearest will after this point at the hovered object, possibly NULL GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); // Is placed in the UpdateMouseOver function //if (ev->ev.type == SDL_MOUSEMOTION && pNearest) // pNearest->ScriptEvent("mousemove"); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); if (ev->ev.type == SDL_MOUSEBUTTONDOWN) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { if (pNearest != m_FocusedObject) { // Update focused object if (m_FocusedObject) m_FocusedObject->HandleMessage(SGUIMessage(GUIM_LOST_FOCUS)); m_FocusedObject = pNearest; m_FocusedObject->HandleMessage(SGUIMessage(GUIM_GOT_FOCUS)); } pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_PRESS_LEFT)); pNearest->ScriptEvent("mouseleftpress"); // Block event, so things on the map (behind the GUI) won't be pressed ret = IN_HANDLED; } else if (m_FocusedObject) { m_FocusedObject->HandleMessage(SGUIMessage(GUIM_LOST_FOCUS)); //if (m_FocusedObject-> TODO SelfishFocus? m_FocusedObject = 0; } break; case SDL_BUTTON_WHEELDOWN: // wheel down if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_DOWN)); pNearest->ScriptEvent("mousewheeldown"); ret = IN_HANDLED; } break; case SDL_BUTTON_WHEELUP: // wheel up if (pNearest) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_WHEEL_UP)); pNearest->ScriptEvent("mousewheelup"); ret = IN_HANDLED; } break; default: break; } } else if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: if (pNearest) { double timeElapsed = get_time() - pNearest->m_LastClickTime[SDL_BUTTON_LEFT]; pNearest->m_LastClickTime[SDL_BUTTON_LEFT] = get_time(); //Double click? if (timeElapsed < SELECT_DBLCLICK_RATE) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_DBLCLICK_LEFT)); pNearest->ScriptEvent("mouseleftdoubleclick"); } else { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_LEFT)); pNearest->ScriptEvent("mouseleftrelease"); } ret = IN_HANDLED; } break; case SDL_BUTTON_RIGHT: if (pNearest) { double timeElapsed = get_time() - pNearest->m_LastClickTime[SDL_BUTTON_RIGHT]; pNearest->m_LastClickTime[SDL_BUTTON_RIGHT] = get_time(); //Double click? if (timeElapsed < SELECT_DBLCLICK_RATE) { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_DBLCLICK_RIGHT)); //pNearest->ScriptEvent("mouserightdoubleclick"); } else { pNearest->HandleMessage(SGUIMessage(GUIM_MOUSE_RELEASE_RIGHT)); //pNearest->ScriptEvent("mouserightrelease"); } ret = IN_HANDLED; } break; } // Reset all states on all visible objects GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ResetStates); // It will have reset the mouse over of the current hovered, so we'll // have to restore that if (pNearest) pNearest->m_MouseHovering = true; } } catch (PS_RESULT e) { UNUSED2(e); debug_warn("CGUI::HandleEvent error"); // TODO Gee: Handle } // JW: what's the difference between mPress and mDown? what's the code below responsible for? /* // Generally if just mouse is clicked if (m_pInput->mDown(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT); } */ // BUTTONUP's effect on m_MouseButtons is handled after // everything else, so that e.g. 'press' handlers (activated // on button up) see which mouse button had been pressed. if (ev->ev.type == SDL_MOUSEBUTTONUP) { switch (ev->ev.button.button) { case SDL_BUTTON_LEFT: case SDL_BUTTON_RIGHT: case SDL_BUTTON_MIDDLE: m_MouseButtons &= ~BIT(ev->ev.button.button); break; default: break; } } // Handle keys for input boxes if (GetFocusedObject()) { if ( (ev->ev.type == SDL_KEYDOWN && ev->ev.key.keysym.sym != SDLK_ESCAPE && !g_keys[SDLK_LCTRL] && !g_keys[SDLK_RCTRL] && !g_keys[SDLK_LALT] && !g_keys[SDLK_RALT]) || ev->ev.type == SDL_HOTKEYDOWN ) { ret = GetFocusedObject()->ManuallyHandleEvent(ev); } // else will return IN_PASS because we never used the button. } return ret; } void CGUI::TickObjects() { CStr action = "tick"; GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, action); // Also update tooltips: // TODO: Efficiency IGUIObject* pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN | GUIRR_GHOST, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); m_Tooltip.Update(pNearest, m_MousePos, this); } void CGUI::SendEventToAll(const CStr& EventName) { // janwas 2006-03-03: spoke with Ykkrosh about EventName case. // when registering, case is converted to lower - this avoids surprise // if someone were to get the case wrong and then not notice their // handler is never called. however, until now, the other end // (sending events here) wasn't converting to lower case, // leading to a similar problem. // now fixed; case is irrelevant since all are converted to lower. GUI::RecurseObject(0, m_BaseObject, &IGUIObject::ScriptEvent, EventName.LowerCase()); } //------------------------------------------------------------------- // Constructor / Destructor //------------------------------------------------------------------- CGUI::CGUI() : m_MouseButtons(0), m_FocusedObject(NULL), m_InternalNameNumber(0) { m_BaseObject = new CGUIDummyObject; m_BaseObject->SetGUI(this); // Construct the parent object for all GUI JavaScript things m_ScriptObject = JS_NewObject(g_ScriptingHost.getContext(), &GUIClass, NULL, NULL); debug_assert(m_ScriptObject != NULL); // How should it handle errors? JS_AddRoot(g_ScriptingHost.getContext(), &m_ScriptObject); // This will make this invisible, not add //m_BaseObject->SetName(BASE_OBJECT_NAME); } CGUI::~CGUI() { if (m_BaseObject) delete m_BaseObject; if (m_ScriptObject) // Let it be garbage-collected JS_RemoveRoot(g_ScriptingHost.getContext(), &m_ScriptObject); } //------------------------------------------------------------------- // Functions //------------------------------------------------------------------- IGUIObject *CGUI::ConstructObject(const CStr& str) { if (m_ObjectTypes.count(str) > 0) return (*m_ObjectTypes[str])(); else { // Error reporting will be handled with the NULL return. return NULL; } } void CGUI::Initialize() { // Add base types! // You can also add types outside the GUI to extend the flexibility of the GUI. // Pyrogenesis though will have all the object types inserted from here. AddObjectType("empty", &CGUIDummyObject::ConstructObject); AddObjectType("button", &CButton::ConstructObject); AddObjectType("image", &CImage::ConstructObject); AddObjectType("text", &CText::ConstructObject); AddObjectType("checkbox", &CCheckBox::ConstructObject); AddObjectType("radiobutton", &CRadioButton::ConstructObject); AddObjectType("progressbar", &CProgressBar::ConstructObject); AddObjectType("minimap", &CMiniMap::ConstructObject); AddObjectType("input", &CInput::ConstructObject); // The following line was commented out, I don't know if that's me or not, or // for what reason, but I'm gonna uncomment, if anything breaks, just let me // know, or if it wasn't I that commented it out, do let me know why. // -- Gee 20-07-2005 AddObjectType("list", &CList::ConstructObject); // AddObjectType("dropdown", &CDropDown::ConstructObject); } void CGUI::Process() { /* // TODO Gee: check if m_pInput is valid, otherwise return /// debug_assert(m_pInput); // Pre-process all objects try { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_PREPROCESS); } catch (PS_RESULT e) { return; } // Check mouse over try { // Only one object can be hovered // check which one it is, if any ! IGUIObject *pNearest = NULL; GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::ChooseMouseOverAndClosest, pNearest); // Now we'll call UpdateMouseOver on *all* objects, // we'll input the one hovered, and they will each // update their own data and send messages accordingly GUI::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::UpdateMouseOver, pNearest); // If pressed if (m_pInput->mPress(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_PRESS_LEFT); } else // If released if (m_pInput->mRelease(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_RELEASE_LEFT); } // Generally if just mouse is clicked if (m_pInput->mDown(NEMM_BUTTON1) && pNearest) { pNearest->HandleMessage(GUIM_MOUSE_DOWN_LEFT); } } catch (PS_RESULT e) { return; } // Post-process all objects try { GUI::RecurseObject(0, m_BaseObject, &IGUIObject::HandleMessage, GUIM_POSTPROCESS); } catch (PS_RESULT e) { return; } */ } void CGUI::Draw() { // Clear the depth buffer, so the GUI is // drawn on top of everything else glClear(GL_DEPTH_BUFFER_BIT); glPushMatrix(); guiLoadIdentity(); try { // Recurse IGUIObject::Draw() with restriction: hidden // meaning all hidden objects won't call Draw (nor will it recurse its children) GUI<>::RecurseObject(GUIRR_HIDDEN, m_BaseObject, &IGUIObject::Draw); } catch (PS_RESULT e) { UNUSED2(e); glPopMatrix(); // TODO Gee: Report error. debug_warn("CGUI::Draw error"); return; } glPopMatrix(); } void CGUI::DrawSprite(CGUISpriteInstance& Sprite, int CellID, const float& Z, const CRect& Rect, const CRect& UNUSED(Clipping)) { // If the sprite doesn't exist (name == ""), don't bother drawing anything if (Sprite.IsEmpty()) return; // TODO: Clipping? glPushMatrix(); glTranslatef(0.0f, 0.0f, Z); Sprite.Draw(Rect, CellID, m_Sprites); glPopMatrix(); } void CGUI::Destroy() { // We can use the map to delete all // now we don't want to cancel all if one Destroy fails map_pObjects::iterator it; for (it = m_pAllObjects.begin(); it != m_pAllObjects.end(); ++it) { try { it->second->Destroy(); } catch (PS_RESULT e) { UNUSED2(e); debug_warn("CGUI::Destroy error"); // TODO Gee: Handle } delete it->second; } for (std::map::iterator it2 = m_Sprites.begin(); it2 != m_Sprites.end(); ++it2) for (std::vector::iterator it3 = it2->second.m_Images.begin(); it3 != it2->second.m_Images.end(); ++it3) delete it3->m_Effects; // Clear all m_pAllObjects.clear(); m_Sprites.clear(); m_Icons.clear(); } void CGUI::UpdateResolution() { // Update ALL cached GUI<>::RecurseObject(0, m_BaseObject, &IGUIObject::UpdateCachedSize ); } void CGUI::AddObject(IGUIObject* pObject) { try { // Add CGUI pointer GUI::RecurseObject(0, pObject, &IGUIObject::SetGUI, this); // Add child to base object m_BaseObject->AddChild(pObject); // can throw // Cache tree GUI<>::RecurseObject(0, pObject, &IGUIObject::UpdateCachedSize); // Loaded GUI::RecurseObject(0, pObject, &IGUIObject::HandleMessage, SGUIMessage(GUIM_LOAD)); } catch (PS_RESULT e) { throw e; } } void CGUI::UpdateObjects() { // We'll fill a temporary map until we know everything // succeeded map_pObjects AllObjects; try { // Fill freshly GUI< map_pObjects >::RecurseObject(0, m_BaseObject, &IGUIObject::AddToPointersMap, AllObjects ); } catch (PS_RESULT e) { // Throw the same error throw e; } // Else actually update the real one m_pAllObjects.swap(AllObjects); } bool CGUI::ObjectExists(const CStr& Name) const { return m_pAllObjects.count(Name) != 0; } IGUIObject* CGUI::FindObjectByName(const CStr& Name) const { map_pObjects::const_iterator it = m_pAllObjects.find(Name); if (it == m_pAllObjects.end()) return NULL; else return it->second; } // private struct used only in GenerateText(...) struct SGenerateTextImage { float m_YFrom, // The image's starting location in Y m_YTo, // The image's end location in Y m_Indentation; // The image width in other words // Some help functions // TODO Gee: CRect => CPoint ? void SetupSpriteCall(const bool Left, SGUIText::SSpriteCall &SpriteCall, const float width, const float y, const CSize &Size, const CStr& TextureName, const float BufferZone, const int CellID) { // TODO Gee: Temp hardcoded values SpriteCall.m_Area.top = y+BufferZone; SpriteCall.m_Area.bottom = y+BufferZone + Size.cy; if (Left) { SpriteCall.m_Area.left = BufferZone; SpriteCall.m_Area.right = Size.cx+BufferZone; } else { SpriteCall.m_Area.left = width-BufferZone - Size.cx; SpriteCall.m_Area.right = width-BufferZone; } SpriteCall.m_CellID = CellID; SpriteCall.m_Sprite = TextureName; m_YFrom = SpriteCall.m_Area.top-BufferZone; m_YTo = SpriteCall.m_Area.bottom+BufferZone; m_Indentation = Size.cx+BufferZone*2; } }; SGUIText CGUI::GenerateText(const CGUIString &string, const CStr& Font, const float &Width, const float &BufferZone, const IGUIObject *pObject) { SGUIText Text; // object we're generating if (string.m_Words.size() == 0) return Text; float x=BufferZone, y=BufferZone; // drawing pointer int from=0; bool done=false; bool FirstLine = true; // Necessary because text in the first line is shorter // (it doesn't count the line spacing) // Images on the left or the right side. vector Images[2]; int pos_last_img=-1; // Position in the string where last img (either left or right) were encountered. // in order to avoid duplicate processing. // Easier to read. bool WordWrapping = (Width != 0); // Go through string word by word for (int i=0; i<(int)string.m_Words.size()-1 && !done; ++i) { // Pre-process each line one time, so we know which floating images // will be added for that line. // Generated stuff is stored in Feedback. CGUIString::SFeedback Feedback; // Preliminary line_height, used for word-wrapping with floating images. float prelim_line_height=0.f; // Width and height of all text calls generated. string.GenerateTextCall(Feedback, Font, string.m_Words[i], string.m_Words[i+1], FirstLine); // Loop through our images queues, to see if images has been added. // Check if this has already been processed. // Also, floating images are only applicable if Word-Wrapping is on if (WordWrapping && i > pos_last_img) { // Loop left/right for (int j=0; j<2; ++j) { for (vector::const_iterator it = Feedback.m_Images[j].begin(); it != Feedback.m_Images[j].end(); ++it) { SGUIText::SSpriteCall SpriteCall; SGenerateTextImage Image; // Y is if no other floating images is above, y. Else it is placed // after the last image, like a stack downwards. float _y; if (Images[j].size() > 0) _y = std::max(y, Images[j].back().m_YTo); else _y = y; // Get Size from Icon database SGUIIcon icon = GetIcon(*it); CSize size = icon.m_Size; Image.SetupSpriteCall((j==CGUIString::SFeedback::Left), SpriteCall, Width, _y, size, icon.m_SpriteName, BufferZone, icon.m_CellID); // Check if image is the lowest thing. Text.m_Size.cy = std::max(Text.m_Size.cy, Image.m_YTo); Images[j].push_back(Image); Text.m_SpriteCalls.push_back(SpriteCall); } } } pos_last_img = std::max(pos_last_img, i); x += Feedback.m_Size.cx; prelim_line_height = std::max(prelim_line_height, Feedback.m_Size.cy); // If Width is 0, then there's no word-wrapping, disable NewLine. if ((WordWrapping && (x > Width-BufferZone || Feedback.m_NewLine)) || i == (int)string.m_Words.size()-2) { // Change 'from' to 'i', but first keep a copy of its value. int temp_from = from; from = i; static const int From=0, To=1; //int width_from=0, width_to=width; float width_range[2]; width_range[From] = BufferZone; width_range[To] = Width - BufferZone; // Floating images are only applicable if word-wrapping is enabled. if (WordWrapping) { // Decide width of the line. We need to iterate our floating images. // this won't be exact because we're assuming the line_height // will be as our preliminary calculation said. But that may change, // although we'd have to add a couple of more loops to try straightening // this problem out, and it is very unlikely to happen noticeably if one // structures his text in a stylistically pure fashion. Even if not, it // is still quite unlikely it will happen. // Loop through left and right side, from and to. for (int j=0; j<2; ++j) { for (vector::const_iterator it = Images[j].begin(); it != Images[j].end(); ++it) { // We're working with two intervals here, the image's and the line height's. // let's find the union of these two. float union_from, union_to; union_from = std::max(y, it->m_YFrom); union_to = std::min(y+prelim_line_height, it->m_YTo); // The union is not empty if (union_to > union_from) { if (j == From) width_range[From] = std::max(width_range[From], it->m_Indentation); else width_range[To] = std::min(width_range[To], Width - it->m_Indentation); } } } } // Reset X for the next loop x = width_range[From]; // Now we'll do another loop to figure out the height of // the line (the height of the largest character). This // couldn't be determined in the first loop (main loop) // because it didn't regard images, so we don't know // if all characters processed, will actually be involved // in that line. float line_height=0.f; for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use // another. CGUIString::SFeedback Feedback2; // Don't attach object, it'll suppress the errors // we want them to be reported in the final GenerateTextCall() // so that we don't get duplicates. string.GenerateTextCall(Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine); // Append X value. x += Feedback2.m_Size.cx; if (WordWrapping && x > width_range[To] && j!=temp_from && !Feedback2.m_NewLine) break; // Let line_height be the maximum m_Height we encounter. line_height = std::max(line_height, Feedback2.m_Size.cy); if (WordWrapping && Feedback2.m_NewLine) break; } // Reset x once more x = width_range[From]; // Move down, because font drawing starts from the baseline y += line_height; // Do the real processing now for (int j=temp_from; j<=i; ++j) { // We don't want to use Feedback now, so we'll have to use // another one. CGUIString::SFeedback Feedback2; // Defaults string.GenerateTextCall(Feedback2, Font, string.m_Words[j], string.m_Words[j+1], FirstLine, pObject); // Iterate all and set X/Y values // Since X values are not set, we need to make an internal // iteration with an increment that will append the internal // x, that is what x_pointer is for. float x_pointer=0.f; vector::iterator it; for (it = Feedback2.m_TextCalls.begin(); it != Feedback2.m_TextCalls.end(); ++it) { it->m_Pos = CPos(x + x_pointer, y); x_pointer += it->m_Size.cx; if (it->m_pSpriteCall) { it->m_pSpriteCall->m_Area += it->m_Pos - CSize(0,it->m_pSpriteCall->m_Area.GetHeight()); } } // Append X value. x += Feedback2.m_Size.cx; Text.m_Size.cx = std::max(Text.m_Size.cx, x+BufferZone); // The first word overrides the width limit, what we // do, in those cases, are just drawing that word even // though it'll extend the object. if (WordWrapping) // only if word-wrapping is applicable { if (Feedback2.m_NewLine) { from = j+1; // Sprite call can exist within only a newline segment, // therefore we need this. Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end()); break; } else if (x > width_range[To] && j==temp_from) { from = j+1; // do not break, since we want it to be added to m_TextCalls } else if (x > width_range[To]) { from = j; break; } } // Add the whole Feedback2.m_TextCalls to our m_TextCalls. Text.m_TextCalls.insert(Text.m_TextCalls.end(), Feedback2.m_TextCalls.begin(), Feedback2.m_TextCalls.end()); Text.m_SpriteCalls.insert(Text.m_SpriteCalls.end(), Feedback2.m_SpriteCalls.begin(), Feedback2.m_SpriteCalls.end()); if (j == (int)string.m_Words.size()-2) done = true; } // Reset X x = 0.f; // Update height of all Text.m_Size.cy = std::max(Text.m_Size.cy, y+BufferZone); FirstLine = false; // Now if we entered as from = i, then we want // i being one minus that, so that it will become // the same i in the next loop. The difference is that // we're on a new line now. i = from-1; } } return Text; } void CGUI::DrawText(SGUIText &Text, const CColor &DefaultColor, const CPos &pos, const float &z, const CRect &clipping) { // TODO Gee: All these really necessary? Some // are defaults and if you changed them // the opposite value at the end of the functions, // some things won't be drawn correctly. glEnable(GL_TEXTURE_2D); glDisable(GL_CULL_FACE); glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable(GL_BLEND); glDisable(GL_ALPHA_TEST); glTexEnvi(GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_MODULATE); if (clipping != CRect()) { double eq[4][4] = { { 0.0, 1.0, 0.0, -clipping.top }, { 1.0, 0.0, 0.0, -clipping.left }, { 0.0, -1.0, 0.0, clipping.bottom }, { -1.0, 0.0, 0.0, clipping.right } }; for (int i=0; i<4; ++i) { glClipPlane(GL_CLIP_PLANE0+i, eq[i]); glEnable(GL_CLIP_PLANE0+i); } } CFont* font = NULL; CStr LastFontName; for (vector::const_iterator it = Text.m_TextCalls.begin(); it != Text.m_TextCalls.end(); ++it) { // If this is just a placeholder for a sprite call, continue if (it->m_pSpriteCall) continue; // Switch fonts when necessary, but remember the last one used if (it->m_Font != LastFontName) { delete font; font = new CFont(it->m_Font); font->Bind(); LastFontName = it->m_Font; } CColor color = it->m_UseCustomColor ? it->m_Color : DefaultColor; glPushMatrix(); // TODO Gee: (2004-09-04) Why are font corrupted if inputted float value? glTranslatef((GLfloat)int(pos.x+it->m_Pos.x), (GLfloat)int(pos.y+it->m_Pos.y), z); glColor4fv(color.FloatArray()); glwprintf(L"%ls", it->m_String.c_str()); // "%ls" is necessary in case m_String contains % symbols glPopMatrix(); } if (font) delete font; for (list::iterator it=Text.m_SpriteCalls.begin(); it!=Text.m_SpriteCalls.end(); ++it) { DrawSprite(it->m_Sprite, it->m_CellID, z, it->m_Area + pos); } // TODO To whom it may concern: Thing were not reset, so // I added this line, modify if incorrect -- if (clipping != CRect()) { for (int i=0; i<4; ++i) glDisable(GL_CLIP_PLANE0+i); } glDisable(GL_TEXTURE_2D); // -- GL } bool CGUI::GetPreDefinedColor(const CStr& name, CColor &Output) { if (m_PreDefinedColors.count(name) == 0) { return false; } else { Output = m_PreDefinedColors[name]; return true; } } void CGUI::ReportParseError(const char *str, ...) { va_list argp; char buffer[512]; memset(buffer,0,sizeof(buffer)); va_start(argp, str); sys_vsnprintf(buffer, sizeof(buffer), str, argp); va_end(argp); // Print header if (m_Errors==0) { LOG(ERROR, LOG_CATEGORY, "*** GUI Tree Creation Errors:"); } // Important, set ParseError to true ++m_Errors; LOG(ERROR, LOG_CATEGORY, buffer); } /** * @callgraph */ void CGUI::LoadXmlFile(const string &Filename) { // Reset parse error // we can later check if this has increased m_Errors = 0; CXeromyces XeroFile; if (XeroFile.Load(Filename.c_str()) != PSRETURN_OK) // Fail silently return; XMBElement node = XeroFile.GetRoot(); // Check root element's (node) name so we know what kind of // data we'll be expecting CStr root_name (XeroFile.GetElementString(node.GetNodeName())); try { if (root_name == "objects") { Xeromyces_ReadRootObjects(node, &XeroFile); // Re-cache all values so these gets cached too. //UpdateResolution(); } else if (root_name == "sprites") { Xeromyces_ReadRootSprites(node, &XeroFile); } else if (root_name == "styles") { Xeromyces_ReadRootStyles(node, &XeroFile); } else if (root_name == "setup") { Xeromyces_ReadRootSetup(node, &XeroFile); } else { debug_warn("CGUI::LoadXmlFile error"); // TODO Gee: Output in log } } catch (PSERROR_GUI& e) { LOG(ERROR, LOG_CATEGORY, "Errors loading GUI file %s (%s)", Filename.c_str(), e.getCode()); return; } // Now report if any other errors occured if (m_Errors > 0) { /// g_console.submit("echo GUI Tree Creation Reports %d errors", m_Errors); } } //=================================================================== // XML Reading Xeromyces Specific Sub-Routines //=================================================================== void CGUI::Xeromyces_ReadRootObjects(XMBElement Element, CXeromyces* pFile) { int el_script = pFile->GetElementID("script"); // Iterate main children // they should all be or