Files
itgmania212121/stepmania/src/ScreenManager.cpp
T

539 lines
17 KiB
C++
Raw Normal View History

2004-03-25 22:13:51 +00:00
/*
* SM_GainFocus/SM_LoseFocus: These are sent to screens when they become the
* topmost screen, or stop being the topmost screen.
*
* A few subtleties:
*
* With delayed screens (eg. ScreenGameplay being pre-loaded by ScreenStage), SM_GainFocus
* isn't sent until the loaded screen actually is activated (put on the stack).
*
* With normal screen loads, the new screen is loaded before the old screen is destroyed.
* This means that the old dtor is called *after* the new ctor. If some global properties
* (eg. GAMESTATE) are being unset by the old screen's destructor, and set by the new
* screen's constructor, they'll happen in the wrong order. Use SM_GainFocus and
* SM_LoseFocus, instead.
*
* SM_LoseFocus is always sent after SM_GainFocus, and vice-versa: you can't gain focus
* if you already have it, and you can't lose focus if you don't have it.
*/
2004-06-08 01:24:17 +00:00
#include "global.h"
2002-05-20 08:59:37 +00:00
#include "ScreenManager.h"
2002-07-23 01:41:40 +00:00
#include "PrefsManager.h"
2002-05-20 08:59:37 +00:00
#include "RageLog.h"
2004-11-25 19:16:46 +00:00
#include "RageUtil.h"
#include "GameState.h"
2004-07-08 00:10:34 +00:00
#include "GameSoundManager.h"
2002-12-30 00:13:47 +00:00
#include "RageDisplay.h"
2003-03-03 10:03:02 +00:00
#include "SongManager.h"
2003-04-18 23:55:20 +00:00
#include "RageTextureManager.h"
#include "ThemeManager.h"
2004-11-25 19:16:46 +00:00
#include "ScreenSystemLayer.h"
2002-05-20 08:59:37 +00:00
ScreenManager* SCREENMAN = NULL; // global and accessable from anywhere in our program
2004-11-26 17:28:47 +00:00
// Screen registration
static map<CString,CreateScreenFn> *g_pmapRegistrees = NULL;
void ScreenManager::Register( const CString& sClassName, CreateScreenFn pfn )
{
if( g_pmapRegistrees == NULL )
g_pmapRegistrees = new map<CString,CreateScreenFn>;
map<CString,CreateScreenFn>::iterator iter = g_pmapRegistrees->find( sClassName );
2004-11-26 19:45:03 +00:00
ASSERT_M( iter == g_pmapRegistrees->end(), ssprintf("Screen class '%s' already registered.", sClassName.c_str()) );
2004-11-26 17:28:47 +00:00
(*g_pmapRegistrees)[sClassName] = pfn;
}
ScreenManager::ScreenManager()
{
m_SystemLayer = NULL;
/* By the time this is constructed, THEME has already been set up and set to
* the current theme. Call ThemeChanged(), to handle the starting theme
* and set up m_SystemLayer. */
ASSERT( THEME );
ASSERT( !THEME->GetCurThemeName().empty() );
this->ThemeChanged();
m_ScreenBuffered = NULL;
2003-11-12 08:13:02 +00:00
m_MessageSendOnPop = SM_None;
2002-05-20 08:59:37 +00:00
}
ScreenManager::~ScreenManager()
{
LOG->Trace( "ScreenManager::~ScreenManager()" );
2002-05-20 08:59:37 +00:00
2002-08-23 06:50:48 +00:00
EmptyDeleteQueue();
2003-03-09 00:55:49 +00:00
// delete current Screens
for( unsigned i=0; i<m_ScreenStack.size(); i++ )
delete m_ScreenStack[i];
delete m_ScreenBuffered;
delete m_SystemLayer;
2002-05-20 08:59:37 +00:00
}
/* This is called when we start up, and when the theme changes or is reloaded. */
2004-09-16 22:05:24 +00:00
void ScreenManager::ThemeChanged()
{
LOG->Trace( "ScreenManager::ThemeChanged" );
// reload common sounds
m_soundStart.Load( THEME->GetPathS("Common","start") );
m_soundCoin.Load( THEME->GetPathS("Common","coin") );
m_soundInvalid.Load( THEME->GetPathS("Common","invalid") );
m_soundScreenshot.Load( THEME->GetPathS("Common","screenshot") );
m_soundBack.Load( THEME->GetPathS("Common","back") );
// reload system layer
2004-11-25 19:16:46 +00:00
SAFE_DELETE( m_SystemLayer );
m_SystemLayer = new ScreenSystemLayer;
m_SystemLayer->RefreshCreditsMessages();
}
2002-08-23 06:50:48 +00:00
void ScreenManager::EmptyDeleteQueue()
{
2003-04-24 19:42:06 +00:00
if(!m_ScreensToDelete.size())
return;
2002-08-23 06:50:48 +00:00
// delete all ScreensToDelete
for( unsigned i=0; i<m_ScreensToDelete.size(); i++ )
2002-08-23 06:50:48 +00:00
SAFE_DELETE( m_ScreensToDelete[i] );
2002-05-20 08:59:37 +00:00
2002-10-24 20:15:24 +00:00
m_ScreensToDelete.clear();
2003-04-24 19:42:06 +00:00
/* Now that we've actually deleted a screen, it makes sense to clear out
* cached textures. */
TEXTUREMAN->DeleteCachedTextures();
2003-04-24 19:51:27 +00:00
TEXTUREMAN->DiagnosticOutput();
2002-08-23 06:50:48 +00:00
}
2002-05-20 08:59:37 +00:00
Screen *ScreenManager::GetTopScreen()
{
if( m_ScreenStack.empty() )
return NULL;
return m_ScreenStack[m_ScreenStack.size()-1];
}
2002-05-20 08:59:37 +00:00
void ScreenManager::Update( float fDeltaTime )
{
// Only update the topmost screen on the stack.
/* Screens take some time to load. If we don't do this, then screens
* receive an initial update that includes all of the time they spent
* loading, which will chop off their tweens.
*
* We don't want to simply cap update times; for example, the stage
* screen sets a 4 second timer, preps the gameplay screen, and then
* displays the prepped screen after the timer runs out; this lets the
* load time be masked (as long as the load takes less than 4 seconds).
* If we cap that large update delta from the screen load, the update
* to load the new screen will come after 4 seconds plus the load time.
*
* So, let's just zero the first update for every screen.
*/
ASSERT( !m_ScreenStack.empty() || m_DelayedScreen != "" ); // Why play the game if there is nothing showing?
if( !m_ScreenStack.empty() )
{
Screen* pScreen = GetTopScreen();
if( pScreen->IsFirstUpdate() )
pScreen->Update( 0 );
else
pScreen->Update( fDeltaTime );
}
m_SystemLayer->Update( fDeltaTime );
EmptyDeleteQueue();
if(m_DelayedScreen.size() != 0)
{
/* We have a screen to display. Delete the current screens and load it. */
2004-03-25 22:13:51 +00:00
ClearScreenStack();
EmptyDeleteQueue();
/* This is the purpose of delayed screen loads: clear out the texture cache
* now, while there's (mostly) nothing loaded. */
TEXTUREMAN->DeleteCachedTextures();
TEXTUREMAN->DiagnosticOutput();
LoadDelayedScreen();
}
2002-05-20 08:59:37 +00:00
}
void ScreenManager::Draw()
{
2004-01-30 05:07:26 +00:00
/* If it hasn't been updated yet, skip the render. We can't call Update(0), since
* that'll confuse the "zero out the next update after loading a screen logic.
* If we don't render, don't call BeginFrame or EndFrame. That way, we won't
* clear the buffer, and we won't wait for vsync. */
if( m_ScreenStack.size() && m_ScreenStack.back()->IsFirstUpdate() )
return;
2004-01-30 05:07:26 +00:00
if( !DISPLAY->BeginFrame() )
return;
if( !m_ScreenStack.empty() && !m_ScreenStack.back()->IsTransparent() ) // top screen isn't transparent
m_ScreenStack.back()->Draw();
else
for( unsigned i=0; i<m_ScreenStack.size(); i++ ) // Draw all screens bottom to top
m_ScreenStack[i]->Draw();
m_SystemLayer->Draw();
DISPLAY->EndFrame();
2002-05-20 08:59:37 +00:00
}
void ScreenManager::Input( const DeviceInput& DeviceI, const InputEventType type, const GameInput &GameI, const MenuInput &MenuI, const StyleInput &StyleI )
{
2002-09-06 00:31:25 +00:00
// LOG->Trace( "ScreenManager::Input( %d-%d, %d-%d, %d-%d, %d-%d )",
// DeviceI.device, DeviceI.button, GameI.controller, GameI.button, MenuI.player, MenuI.button, StyleI.player, StyleI.col );
2002-05-20 08:59:37 +00:00
// pass input only to topmost state
if( !m_ScreenStack.empty() )
m_ScreenStack.back()->Input( DeviceI, type, GameI, MenuI, StyleI );
2002-05-20 08:59:37 +00:00
}
2004-11-26 17:28:47 +00:00
Screen* ScreenManager::MakeNewScreen( const CString &sName )
{
2003-12-18 08:53:38 +00:00
/* By default, RageSounds handles the song timer. When we change screens, reset this;
2004-06-06 22:04:08 +00:00
* screens turn this off in SM_GainFocus if they handle timers themselves (edit).
* XXX: screens should turn this on in SM_LoseFocus if they handle timers themselves, too */
2003-12-18 08:53:38 +00:00
SOUND->HandleSongTimer( true );
2003-12-21 02:54:23 +00:00
/* Cleanup song data. This can free up a fair bit of memory, so do it before
* creating the new screen, to lower peak memory usage slightly. */
SONGMAN->Cleanup();
2004-04-11 01:45:29 +00:00
RageTimer t;
2004-11-26 17:28:47 +00:00
LOG->Trace( "Loading screen name '%s'", sName.c_str() );
CString sClassName = sName;
// Look up the class in the metrics group sName
if( THEME->HasMetric(sClassName,"Class") )
sClassName = THEME->GetMetric(sClassName,"Class");
map<CString,CreateScreenFn>::iterator iter = g_pmapRegistrees->find( sClassName );
ASSERT_M( iter != g_pmapRegistrees->end(), ssprintf("Screen '%s' has an invalid class '%s'",sName.c_str(),sClassName.c_str()) )
CreateScreenFn pfn = iter->second;
Screen* ret = pfn( sName );
LOG->Trace( "Loaded '%s' ('%s') in %f", sName.c_str(), sClassName.c_str(), t.GetDeltaTime());
2002-12-21 07:54:24 +00:00
2002-12-29 23:37:41 +00:00
/* Loading probably took a little while. Let's reset stats. This prevents us
2002-12-21 07:54:24 +00:00
* from displaying an unnaturally low FPS value, and the next FPS value we
* display will be accurate, which makes skips in the initial tween-ins more
* apparent. */
DISPLAY->ResetStats();
return ret;
}
2004-11-26 17:28:47 +00:00
void ScreenManager::PrepNewScreen( const CString &sClassName )
{
2002-09-04 23:40:01 +00:00
ASSERT(m_ScreenBuffered == NULL);
m_ScreenBuffered = MakeNewScreen(sClassName);
}
void ScreenManager::LoadPreppedScreen()
{
ASSERT( m_ScreenBuffered != NULL);
2004-01-30 05:07:26 +00:00
SetFromNewScreen( m_ScreenBuffered, false );
2002-09-04 23:40:01 +00:00
m_ScreenBuffered = NULL;
}
void ScreenManager::DeletePreppedScreen()
{
SAFE_DELETE( m_ScreenBuffered );
TEXTUREMAN->DeleteCachedTextures();
}
2004-03-25 22:13:51 +00:00
/* Remove all screens from the stack, sending a SM_LoseFocus message to the top.
* (There's no need to send them to any lower screens; they don't have focus anyway,
* and received the message when they actually lost it. */
void ScreenManager::ClearScreenStack()
{
if( m_ScreenStack.size() )
m_ScreenStack.back()->HandleScreenMessage( SM_LoseFocus );
// move current screen(s) to ScreenToDelete
m_ScreensToDelete.insert(m_ScreensToDelete.end(), m_ScreenStack.begin(), m_ScreenStack.end());
m_ScreenStack.clear();
}
2004-01-30 05:07:26 +00:00
/* Add a screen to m_ScreenStack. If Stack is true, it's added to the stack; otherwise any
* current screens are removed. This is the only function that adds to m_ScreenStack. */
void ScreenManager::SetFromNewScreen( Screen *pNewScreen, bool Stack )
{
RefreshCreditsMessages();
2004-01-30 05:07:26 +00:00
if( !Stack )
2004-03-25 22:13:51 +00:00
ClearScreenStack();
2002-10-31 04:23:39 +00:00
m_ScreenStack.push_back( pNewScreen );
2003-11-25 21:57:00 +00:00
PostMessageToTopScreen( SM_GainFocus, 0 );
}
2004-11-26 17:28:47 +00:00
void ScreenManager::SetNewScreen( const CString &sClassName )
2002-05-20 08:59:37 +00:00
{
m_DelayedScreen = sClassName;
/* If we're not delaying screen loads, load it now. Otherwise, we'll load
* it on the next iteration. Only delay if we already have a screen
* loaded; otherwise, there's no reason to delay. */
if(!PREFSMAN->m_bDelayedScreenLoad) // || m_ScreenStack.empty() )
LoadDelayedScreen();
}
void ScreenManager::LoadDelayedScreen()
{
retry:
CString sClassName = m_DelayedScreen;
m_DelayedScreen = "";
/* If we prepped a screen but didn't use it, nuke it. */
SAFE_DELETE( m_ScreenBuffered );
2003-03-09 00:55:49 +00:00
Screen* pOldTopScreen = m_ScreenStack.empty() ? NULL : m_ScreenStack.back();
// It makes sense that ScreenManager should allocate memory for a new screen since it
// deletes it later on. This also convention will reduce includes because screens won't
// have to include each other's headers of other screens.
Screen* pNewScreen = MakeNewScreen(sClassName);
2003-03-09 00:55:49 +00:00
if( pOldTopScreen!=NULL && m_ScreenStack.back()!=pOldTopScreen )
{
// While constructing this Screen, it's constructor called
// SetNewScreen again! That SetNewScreen Command should
// override this older one.
SAFE_DELETE( pNewScreen );
return;
}
if( PREFSMAN->m_bDelayedScreenLoad && m_DelayedScreen != "" )
{
/* Same deal: the ctor called SetNewScreen again. Delete the screen
* we just made, but don't delay again. */
SAFE_DELETE( pNewScreen );
goto retry;
}
bool bWasOnSystemMenu = GAMESTATE->m_bIsOnSystemMenu;
/* If this is a system menu, don't let the operator key touch it!
However, if you add an options screen, please include it here -- Miryokuteki */
if( sClassName == "ScreenOptionsMenu" ||
sClassName == "ScreenMachineOptions" ||
sClassName == "ScreenInputOptions" ||
sClassName == "ScreenGraphicOptions" ||
sClassName == "ScreenGameplayOptions" ||
sClassName == "ScreenMapControllers" ||
sClassName == "ScreenAppearanceOptions" ||
sClassName == "ScreenEdit" ||
sClassName == "ScreenEditMenu" ||
sClassName == "ScreenSoundOptions" )
2003-03-09 00:55:49 +00:00
GAMESTATE->m_bIsOnSystemMenu = true;
else
GAMESTATE->m_bIsOnSystemMenu = false;
// If we're exiting a system menu, persist settings in case we don't exit normally
if( bWasOnSystemMenu && !GAMESTATE->m_bIsOnSystemMenu )
PREFSMAN->SaveGlobalPrefsToDisk();
2004-01-30 05:07:26 +00:00
LOG->Trace("... SetFromNewScreen");
SetFromNewScreen( pNewScreen, false );
2002-05-20 08:59:37 +00:00
}
2004-11-26 17:28:47 +00:00
void ScreenManager::AddNewScreenToTop( const CString &sClassName, ScreenMessage messageSendOnPop )
2003-02-19 05:46:09 +00:00
{
/* Send this before making the new screen, since it might set things that will be re-set
* in the new screen's ctor. */
if( m_ScreenStack.size() )
m_ScreenStack.back()->HandleScreenMessage( SM_LoseFocus );
2003-02-19 05:46:09 +00:00
Screen* pNewScreen = MakeNewScreen(sClassName);
2004-01-30 05:07:26 +00:00
SetFromNewScreen( pNewScreen, true );
2003-11-12 08:13:02 +00:00
m_MessageSendOnPop = messageSendOnPop;
2003-02-19 05:46:09 +00:00
}
2003-03-03 10:03:02 +00:00
#include "ScreenPrompt.h"
#include "ScreenTextEntry.h"
#include "ScreenMiniMenu.h"
2004-11-26 17:28:47 +00:00
void ScreenManager::Prompt( ScreenMessage SM_SendWhenDone, const CString &sText, bool bYesNo, bool bDefaultAnswer, void(*OnYes)(void*), void(*OnNo)(void*), void* pCallbackData )
2002-05-20 08:59:37 +00:00
{
if( m_ScreenStack.size() )
m_ScreenStack.back()->HandleScreenMessage( SM_LoseFocus );
// add the new state onto the back of the array
2004-01-30 05:07:26 +00:00
Screen *pNewScreen = new ScreenPrompt( sText, bYesNo, bDefaultAnswer, OnYes, OnNo, pCallbackData);
SetFromNewScreen( pNewScreen, true );
m_MessageSendOnPop = SM_SendWhenDone;
}
2002-05-20 08:59:37 +00:00
2004-01-30 05:07:26 +00:00
void ScreenManager::TextEntry( ScreenMessage SM_SendWhenDone, CString sQuestion, CString sInitialAnswer, void(*OnOK)(CString sAnswer), void(*OnCancel)() )
{
if( m_ScreenStack.size() )
m_ScreenStack.back()->HandleScreenMessage( SM_LoseFocus );
2002-05-20 08:59:37 +00:00
// add the new state onto the back of the array
2004-01-30 05:07:26 +00:00
Screen *pNewScreen = new ScreenTextEntry( "ScreenTextEntry", sQuestion, sInitialAnswer, OnOK, OnCancel );
SetFromNewScreen( pNewScreen, true );
m_MessageSendOnPop = SM_SendWhenDone;
2002-05-20 08:59:37 +00:00
}
void ScreenManager::MiniMenu( Menu* pDef, ScreenMessage SM_SendOnOK, ScreenMessage SM_SendOnCancel )
2003-02-18 23:15:38 +00:00
{
if( m_ScreenStack.size() )
m_ScreenStack.back()->HandleScreenMessage( SM_LoseFocus );
2003-02-18 23:15:38 +00:00
// add the new state onto the back of the array
2004-01-30 05:07:26 +00:00
Screen *pNewScreen = new ScreenMiniMenu( pDef, SM_SendOnOK, SM_SendOnCancel );
SetFromNewScreen( pNewScreen, true );
2003-02-18 23:15:38 +00:00
}
2002-07-27 19:29:51 +00:00
void ScreenManager::PopTopScreen( ScreenMessage SM )
2002-05-20 08:59:37 +00:00
{
Screen* pScreenToPop = m_ScreenStack.back(); // top menu
2003-11-25 21:57:00 +00:00
pScreenToPop->HandleScreenMessage( SM_LoseFocus );
2002-10-31 04:11:08 +00:00
m_ScreenStack.erase(m_ScreenStack.end()-1, m_ScreenStack.end());
2002-10-31 04:23:39 +00:00
m_ScreensToDelete.push_back( pScreenToPop );
2003-11-12 08:13:02 +00:00
/* Post to the new top. This must be done now; otherwise, we'll have a single
* frame between popping and these messages, which can result in a frame where eg.
2004-01-26 22:19:33 +00:00
* input is accepted where it shouldn't be. Watch out; sending m_MessageSendOnPop
* might push another screen (eg. editor menu -> PlayerOptions), which will set
* a new m_MessageSendOnPop. */
ScreenMessage MessageToSend = m_MessageSendOnPop;
m_MessageSendOnPop = SM_None;
SendMessageToTopScreen( SM );
SendMessageToTopScreen( SM_GainFocus );
2004-01-26 22:19:33 +00:00
SendMessageToTopScreen( MessageToSend );
2002-05-20 08:59:37 +00:00
}
2003-03-25 21:17:29 +00:00
void ScreenManager::PostMessageToTopScreen( ScreenMessage SM, float fDelay )
2002-05-20 08:59:37 +00:00
{
Screen* pTopScreen = m_ScreenStack.back();
2003-03-25 21:17:29 +00:00
pTopScreen->PostScreenMessage( SM, fDelay );
}
void ScreenManager::SendMessageToTopScreen( ScreenMessage SM )
{
Screen* pTopScreen = m_ScreenStack.back();
pTopScreen->HandleScreenMessage( SM );
2002-05-20 08:59:37 +00:00
}
2004-11-26 17:28:47 +00:00
void ScreenManager::SystemMessage( const CString &sMessage )
2002-05-20 08:59:37 +00:00
{
2003-09-15 20:44:25 +00:00
LOG->Trace( "%s", sMessage.c_str() );
m_SystemLayer->SystemMessage( sMessage );
2002-05-20 08:59:37 +00:00
}
2004-11-26 17:28:47 +00:00
void ScreenManager::SystemMessageNoAnimate( const CString &sMessage )
2003-11-20 11:55:45 +00:00
{
2004-04-23 00:26:51 +00:00
// LOG->Trace( "%s", sMessage.c_str() ); // don't log because the caller is likely calling us every frame
2003-11-20 11:55:45 +00:00
m_SystemLayer->SystemMessageNoAnimate( sMessage );
}
void ScreenManager::RefreshCreditsMessages()
{
m_SystemLayer->RefreshCreditsMessages();
2004-03-13 10:26:51 +00:00
/* This is called when GAMESTATE->m_bSideIsJoined changes. */
CString joined;
FOREACH_PlayerNumber( pn )
{
if( GAMESTATE->m_bSideIsJoined[pn] )
{
if( joined != "" )
joined += ", ";
joined += ssprintf( "P%i", pn+1 );
}
}
if( joined == "" )
joined = "none";
LOG->MapLog( "JOINED", "Players joined: %s", joined.c_str() );
2003-02-12 07:52:20 +00:00
}
2004-01-03 20:34:16 +00:00
void ScreenManager::ReloadCreditsText()
{
m_SystemLayer->ReloadCreditsText();
}
/* Always play these sounds, even if we're in a silent attract loop. */
void ScreenManager::PlayStartSound()
{
RageSoundParams p;
p.m_Volume = PREFSMAN->m_fSoundVolume;
m_soundStart.Play( &p );
}
void ScreenManager::PlayCoinSound()
{
RageSoundParams p;
p.m_Volume = PREFSMAN->m_fSoundVolume;
m_soundCoin.Play( &p );
}
void ScreenManager::PlayInvalidSound()
{
RageSoundParams p;
p.m_Volume = PREFSMAN->m_fSoundVolume;
m_soundInvalid.Play( &p );
}
void ScreenManager::PlayScreenshotSound()
{
RageSoundParams p;
p.m_Volume = PREFSMAN->m_fSoundVolume;
m_soundScreenshot.Play( &p );
}
void ScreenManager::PlayBackSound()
{
RageSoundParams p;
p.m_Volume = PREFSMAN->m_fSoundVolume;
m_soundBack.Play( &p );
}
2004-06-08 01:24:17 +00:00
/*
* (c) 2001-2003 Chris Danford, Glenn Maynard
* All rights reserved.
*
* Permission is hereby granted, free of charge, to any person obtaining a
* copy of this software and associated documentation files (the
* "Software"), to deal in the Software without restriction, including
* without limitation the rights to use, copy, modify, merge, publish,
* distribute, and/or sell copies of the Software, and to permit persons to
* whom the Software is furnished to do so, provided that the above
* copyright notice(s) and this permission notice appear in all copies of
* the Software and that both the above copyright notice(s) and this
* permission notice appear in supporting documentation.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS
* OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF
* MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT OF
* THIRD PARTY RIGHTS. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR HOLDERS
* INCLUDED IN THIS NOTICE BE LIABLE FOR ANY CLAIM, OR ANY SPECIAL INDIRECT
* OR CONSEQUENTIAL DAMAGES, OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
* OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR
* OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR
* PERFORMANCE OF THIS SOFTWARE.
*/