123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608609610611612613614615616617618619620621622623624625626627628629630631632633634635636637638639640641642643644645646647648649650651652653654655656657658659660661662663664665666667668669670671672673674675676677678679680681682683684685686687688689690691692693694695696697698699700701702703704705706707708709710711712713714715716717718719720721722723724725726727728729730731732733734735736737738739740741742743744745746747748749750751752753754755756757758759760761762763764765766767768769770771772773774775776777778779780781782783784785786787788789790791792793794795796797798799800801802803804805806807808809810811812813814815816817818819820821822823824825826827828829830831832833834835836837838839840841842843844845 |
- ////////////////////////////////////////////////////////////////////////////////
- //
- // Copyright 2016 RWS Inc, All Rights Reserved
- //
- // This program is free software; you can redistribute it and/or modify
- // it under the terms of version 2 of the GNU General Public License as published by
- // the Free Software Foundation
- //
- // This program is distributed in the hope that it will be useful,
- // but WITHOUT ANY WARRANTY; without even the implied warranty of
- // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
- // GNU General Public License for more details.
- //
- // You should have received a copy of the GNU General Public License along
- // with this program; if not, write to the Free Software Foundation, Inc.,
- // 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA
- //
- //////////////////////////////////////////////////////////////////////////////
- //
- // RtPlay.CPP
- //
- // History:
- // 09/21/95 JMI Started.
- //
- //////////////////////////////////////////////////////////////////////////////
- //
- // This class controls the flow of the streaming of a file. By putting this
- // at a higher level than the basic read stuff, we have allowed one to write
- // their own flow control for a stream that could override or use any of the
- // components used by this module (i.e., CFileWin, CFilter, CDispatch, CRes,
- // etc.).
- //
- //////////////////////////////////////////////////////////////////////////////
- //
- // There are many states that the streaming can be in. The following table
- // shows the states that can be changed from/to. It is not necessary to
- // handle transitions not in this table (this should make message handler
- // logic much simpler) (you may want to use a default: case in your switch,
- // if you have one, that displays a warning if you get a transition not in
- // this table).
- //
- // From\To | Stopped | Starting | Begin | Playing | Aborting | Ending | Pause
- // ---------+---------+----------+-------+---------+----------+--------+------
- // Stopped | -- | Yes | No | No | No | No | No
- // Starting | No | -- | Yes | No | Yes | No | No
- // Begin | No | No | -- | Yes | Yes | No | No
- // Playing | No | No | No | -- | Yes | Yes | Yes
- // Aborting | Yes | No | No | No | -- | No | No
- // Ending | Yes | No | No | No | Yes | -- | No
- // Pause | No | No | No | Yes | Yes | No | --
- //
- // Currently there are a total of 13 transitions that could be handled by
- // your handler.
- //
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- // C Headers.
- //////////////////////////////////////////////////////////////////////////////
- #include <malloc.h>
- //////////////////////////////////////////////////////////////////////////////
- // Blue Headers.
- //////////////////////////////////////////////////////////////////////////////
- #include "System.h"
- #include "bdebug.h"
- #include "bcritic.h"
- //////////////////////////////////////////////////////////////////////////////
- // Green Headers.
- //////////////////////////////////////////////////////////////////////////////
- #include "rtplay.h"
- #include "rttypes.h"
- //////////////////////////////////////////////////////////////////////////////
- // Orange Headers.
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- // Yellow Headers.
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- // Module specific macros.
- //////////////////////////////////////////////////////////////////////////////
- // If behind more than MAX_LAG a TRACE displays how far behind we are in
- // debug build only.
- #define MAX_LAG 500L // One half second.
- // Default size for file window.
- #define DEF_WINDOW_SIZE (60L * 1024L)
- #define DEF_INPUTPANE_SIZE (20L * 1024L)
- #define DEF_FILTERPANE_SIZE (20L * 1024L)
- // Commands from the RTF.
- #define CMD_INIT 0x0000
- #define CMD_SUSPENDREADS 0x0001
- #define CMD_RESUMEREADS 0x0002
- #define CMD_EOC 0x0003
- #define CMD_ENDING 0x0004
- #define CMD_STREAMHIT 0x0005
- //////////////////////////////////////////////////////////////////////////////
- // Module specific typedefs.
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- // Module specific (static) variables.
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- // Construction/Destruction Functions.
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- //
- // Default constructor.
- //
- //////////////////////////////////////////////////////////////////////////////
- CRtPlay::CRtPlay()
- {
- // These values are set only on construction, or by the user.
- m_lWindowSize = DEF_WINDOW_SIZE;
- m_lInputPaneSize = DEF_INPUTPANE_SIZE;
- m_lFilterPaneSize = DEF_FILTERPANE_SIZE;
- Set();
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Destructor.
- //
- //////////////////////////////////////////////////////////////////////////////
- CRtPlay::~CRtPlay()
- {
- Reset();
- }
- //////////////////////////////////////////////////////////////////////////////
- // Internal Functions.
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- //
- // Sets variables w/o regard to current values.
- //
- //////////////////////////////////////////////////////////////////////////////
- void CRtPlay::Set(void)
- {
- // Set data handler (where we get direction from the RT file).
- m_dispatch.SetDataHandler( RT_TYPE_RTFINFO, RtInfoCallStatic);
- m_dispatch.SetUserVal(RT_TYPE_RTFINFO, (long)this);
-
- // Let resource know who the dispatcher is.
- m_res.SetDispatcher(&m_dispatch);
- // Let dispatcher know who the filter is.
- m_dispatch.SetFilter(&m_filter);
- // Let dispatcher know what time base to use.
- m_dispatch.SetTimeFunc(CRtTime::GetTime, (long)&m_rttime);
- // Let filter know who the file window is.
- m_filter.SetFileWin(&m_filewin);
- // Let file win know what time base to use.
- m_filewin.SetTimeFunc(CRtTime::GetTime, (long)&m_rttime);
- m_usState = RT_STATE_STOPPED;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Resets variables. Performs deallocation if necessary.
- //
- //////////////////////////////////////////////////////////////////////////////
- void CRtPlay::Reset(void)
- {
- Set();
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Adds an RT_TYPE_RTFINFO chunk to the dispatcher with the specified command
- // and parameters.
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::CreateCmd(USHORT usCmd, long lTime, long lParm1, long lParm2)
- {
- short sRes = 0; // Assume success.
- long lSize = sizeof(usCmd) + sizeof(lParm1) + sizeof(lParm2);
- UCHAR* puc = (UCHAR*)malloc(lSize);
- // If successful . . .
- if (puc != NULL)
- {
- CNFile file;
- file.Open(puc, lSize, ENDIAN_BIG);
- file.Write(&usCmd);
- file.Write(&lParm1);
- file.Write(&lParm2);
- file.Close();
- // Send chunk to dispatcher.
- if (m_dispatch.AddItem(puc, lSize, RT_TYPE_RTFINFO, 0, lTime) == 0)
- {
- // Success.
- }
- else
- {
- TRACE("CreateCmd(): Unable to add command to dispatcher.\n");
- sRes = -1;
- }
- // If any errors occurred after allocation . . .
- if (sRes != 0)
- {
- free(puc);
- }
- }
- else
- {
- TRACE("CreateCmd(): Unable to allocate chunk for command.\n");
- sRes = -1;
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Handles data callbacks from dispatch.
- // Returns RET_FREE if puc should be freed and RET_DONTFREE, otherwise.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::RtInfoCall( UCHAR* puc, long lSize, USHORT usType, UCHAR ucFlags,
- long lTime)
- {
- short sError = 0;
- // If this is the init/first chunk . . .
- if (ucFlags & RT_FLAG_INIT)
- {
- // Suspend reading (remembers time until next read).
- m_filewin.Suspend();
- // Set time to INIT time.
- m_rttime.SetTime(lTime);
- // Resume reading (next read will occur based on time until next read at
- // Suspend() call above).
- m_filewin.Start();
- }
- else
- {
- #if _DEBUG
- if (lTime + MAX_LAG < m_rttime.GetTime())
- {
- TRACE("RtInfoCall(): %ld ms behind!\n",
- m_rttime.GetTime() - lTime);
- }
- #endif // _DEBUG
- }
- CNFile file;
- if (file.Open(puc, lSize, ENDIAN_BIG) == 0)
- {
- USHORT usCmd;
- ULONG ulChannels;
- long lTimeVal;
- long lVal;
- // Get type of info.
- file.Read(&usCmd);
- switch (usCmd)
- {
- case CMD_INIT:
- // Gives us info on this rtf. This is, basically, the header and
- // could contain the record table, etc. This should be the very
- // first chunk of any RTF.
- // Read the maximum read rate in bytes per second.
- file.Read(&lVal);
- ASSERT(lVal != 0L);
- // Compute the input interval.
- m_filewin.SetInputInterval((m_lInputPaneSize * 1000) / lVal);
- break;
- case CMD_SUSPENDREADS:
- // Suspend the file window until lTimeVal.
- file.Read(&lTimeVal);
- if (m_filewin.Suspend() == 0)
- {
- // Add an RTFINFO chunk at the specified time to tell us to restart.
- if (CreateCmd(CMD_RESUMEREADS, lTimeVal, 0L, 0L) == 0)
- {
- // Success.
- }
- else
- {
- TRACE("RtInfoCall(): Failed to add RTFINFO RESUME command chunk "
- "to the dispatcher.\n");
- sError = -3;
- }
- }
- else
- {
- TRACE("RtInfoCall(): Failed to suspend file window! Aborting.\n");
- sError = -2;
- }
- break;
- case CMD_RESUMEREADS:
- // Restart file window.
- if (m_filewin.Start() == 0)
- {
- // Success.
- }
- else
- {
- TRACE("RtInfoCall(): Failed to restart file window! Aborting.\n");
- sError = -4;
- }
- break;
- case CMD_EOC:
- // Get the channels that are done so far.
- file.Read(&ulChannels);
- // Add to done mask.
- m_ulChannelsDone |= ulChannels;
- // If all current channels done . . .
- if ((m_ulChannelsDone & m_filter.GetFilter()) == m_filter.GetFilter())
- {
- // Attempt to stop file window, if still running . . .
- if (m_filewin.Suspend() == 0)
- {
- // Done streaming (processing may need to continue).
- SetState(RT_STATE_ENDING);
- // Dispatcher should be done.
- ASSERT(m_dispatch.IsEmpty() == TRUE);
- }
- else
- {
- TRACE("RtInfoCall(): Failed to suspend file window! All channels "
- "complete.\n");
- sError = -5;
- }
- }
- break;
- case CMD_STREAMHIT:
- // This is simply data to provide minimum throughput for the stream.
- // On a CD this can be used to periodically hit the disc to keep it
- // from straying too far ahead.
- break;
- default:
- TRACE("RtInfoCall(): Invalid type.\n");
- sError = -3;
- break;
- }
- file.Close();
- }
- else
- {
- TRACE("RtInfoCall(): Unable to open info.\n");
- sError = -1;
- }
- // If we got any errors . . .
- if (sError != 0)
- {
- if (Abort() == 0)
- {
- }
- else
- {
- TRACE("RtInfoCall(): Failed to abort current play after error.\n");
- }
- }
- // Have dispatcher free this buffer for us.
- return RET_FREE;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Callback dispatcher (calls the implied this version).
- // Returns RET_FREE if puc should be freed and RET_DONTFREE, otherwise.
- // (static)
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::RtInfoCallStatic( UCHAR* puc, long lSize, USHORT usType,
- UCHAR ucFlags, long lTime, long l_pRtPlay)
- {
- return ((CRtPlay*)l_pRtPlay)->RtInfoCall(puc, lSize, usType, ucFlags, lTime);
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Sets the state to the new state. Generates messages if a state change
- // occurs.
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::SetState(USHORT usState)
- {
- short sRes = 0; // Assume success.
- // If new state . . .
- if (m_usState != usState)
- {
- // Currently there is a one to one correspondence between the
- // RT_STATE_*'s and the RT_MSG_*'s so we simply copy the state
- // as the message. If the correspondence is somehow removed,
- // this should be updated. Of course, if you simply change the
- // macros, you'll never see this until you figure out that there's
- // a problem.
- USHORT usMsg = usState;
- short sNum = m_dispatch.SendHandlerMessage(usMsg);
- if (sNum == 0)
- {
- // All message handlers okayed the new state. Change it.
- m_usState = usState;
- }
- else
- {
- TRACE("SetState(): Attempt to change to %s state rejected by %d "
- "handlers!\n",
- (usState == RT_STATE_STOPPED ? "STOPPED" :
- (usState == RT_STATE_STARTING ? "STARTING" :
- (usState == RT_STATE_BEGIN ? "BEGIN" :
- (usState == RT_STATE_PLAYING ? "PLAYING" :
- (usState == RT_STATE_PAUSE ? "PAUSE" :
- (usState == RT_STATE_ABORTING ? "ABORTING" :
- (usState == RT_STATE_ENDING ? "ENDING" : "UNKNOWN"
- ) ) ) ) ) ) ), sNum);
- // At least one handler rejected the state change.
- sRes = 1;
- }
-
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Handles current state. Called by Blue's critical handler list.
- //
- //////////////////////////////////////////////////////////////////////////////
- void CRtPlay::Critical(void)
- {
- short sError = 0;
- switch (m_usState)
- {
- case RT_STATE_STOPPED:
- // Done.
- if (Blu_RemoveCritical(CriticalStatic) == 0)
- {
- }
- else
- {
- TRACE("Critical(): Unable to remove critical handler.\n");
- }
- break;
- case RT_STATE_STARTING:
- // If all handlers ready . . .
- if (m_dispatch.SendHandlerMessage(RT_STATE_BEGIN) == 0)
- {
- // Switch to playing state.
- SetState(RT_STATE_PLAYING);
- }
- break;
- case RT_STATE_BEGIN:
- // Should not happen.
- TRACE("Critical(): SUX! Problem: this state should not be reached!\n");
- sError = -1;
- break;
- case RT_STATE_PLAYING:
- break;
- case RT_STATE_PAUSE:
- break;
- case RT_STATE_ENDING:
- // Wait for dispatcher to empty . . .
- if (m_dispatch.IsEmpty() == FALSE)
- {
- break;
- }
- // Intentional fall through to RT_STATE_ABORTING.
- case RT_STATE_ABORTING:
- // Try to end as soon as possible.
- if (m_dispatch.SendHandlerMessage(RT_STATE_STOPPED) == 0)
- {
- // Attempt to stop dispatcher . . .
- if (m_dispatch.Suspend() == 0)
- {
- }
- else
- {
- TRACE("Critical(): Unable to suspend dispatcher.\n");
- }
- SetState(RT_STATE_STOPPED);
- }
- break;
- }
- if (sError != 0)
- {
- Abort();
- }
- }
- //////////////////////////////////////////////////////////////////////////////
- // Public methods.
- //////////////////////////////////////////////////////////////////////////////
- //////////////////////////////////////////////////////////////////////////////
- //
- // Opens a stream file for play.
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::Open(char* pszFileName)
- {
- short sRes = 0; // Assume success.
- if (Close() == 0)
- {
- // Attempt to open file window . . .
- if (m_filewin.Open(pszFileName) == 0)
- {
- // Attempt to set window and pane sizes . . .
- if (m_filewin.SetSize(m_lWindowSize, m_lInputPaneSize,
- m_lFilterPaneSize) == 0)
- {
- // Successfully opened.
- }
- else
- {
- TRACE("Open(\"%s\"): Unable to set file window and pane sizes.\n",
- pszFileName);
- sRes = -3;
- }
- // If any errors occur after opening file . . .
- if (sRes != 0)
- {
- // Close it.
- Close();
- }
- }
- else
- {
- TRACE("Open(\"%s\"): Unable to open file window.\n", pszFileName);
- sRes = -2;
- }
- }
- else
- {
- TRACE("Open(\"%s\"): Unable to close currently open stream file.\n", pszFileName);
- sRes = -1;
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Closes an open stream file.
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::Close(void)
- {
- short sRes = 0; // Assume success.
- // If stopped . . .
- if (m_usState == RT_STATE_STOPPED)
- {
- if (m_filewin.Close() == 0)
- {
- // Successfully closed.
- // Tell resource manager to free unused resources.
- m_res.FreeAll();
- }
- else
- {
- TRACE("Close(): Failed to close the file window!\n");
- sRes = -2;
- }
- }
- else
- {
- TRACE("Close(): The stream file is not stopped!\n");
- sRes = -1;
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Starts play.
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::Play(void)
- {
- short sRes = 0; // Assume success.
- // If stopped . . .
- if (m_usState == RT_STATE_STOPPED)
- {
- // Set the time to 0.
- m_rttime.SetTime(0L);
- // Start with no channels done.
- m_ulChannelsDone = 0;
- // Attempt to start the file window . . .
- if (m_filewin.Start() == 0)
- {
- // Attempt to start the dispatching . . .
- if (m_dispatch.Start() == 0)
- {
- // Attempt to start critical handler . . .
- if (Blu_AddCritical(CriticalStatic, (long)this) == 0)
- {
- if (SetState(RT_STATE_STARTING) == 0)
- {
- // Successfully changed to starting state.
- }
- else
- {
- TRACE("Play(): At least one handler rejected starting.\n");
- sRes = -4;
- }
- // If any errors occurred after we started the critical handler . . .
- if (sRes != 0)
- {
- Blu_RemoveCritical(CriticalStatic);
- }
- }
- else
- {
- TRACE("Play(): Unable to add critical handler.\n");
- sRes = -5;
- }
- // If any errors occurred after we started the dispatcher . . .
- if (sRes != 0)
- {
- m_dispatch.Suspend();
- }
- }
- else
- {
- TRACE("Play(): The dispatcher did not start!\n");
- sRes = -3;
- }
- // If any errors occurred after we started the file window . . .
- if (sRes != 0)
- {
- m_filewin.Suspend();
- }
- }
- else
- {
- TRACE("Play(): The file window did not start!\n");
- sRes = -2;
- }
-
- // If any errors occurred after we started the file window . . .
- if (sRes != 0)
- {
- m_filewin.Suspend();
- }
- }
- else
- {
- TRACE("Play(): The stream file is not stopped!\n");
- sRes = -1;
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Aborts current play.
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::Abort(void)
- {
- short sRes = 0; // Assume success.
- // If not stopped . . .
- if (m_usState != RT_STATE_STOPPED)
- {
- if (m_filewin.IsActive() == TRUE)
- {
- // Suspend file window processing . . .
- if (m_filewin.Suspend() == 0)
- {
- }
- else
- {
- TRACE("Abort(): Unable to suspend file window.\n");
- sRes = -2;
- }
- }
- if (m_dispatch.IsActive() == TRUE)
- {
- // Suspend dispatching . . .
- if (m_dispatch.Suspend() == 0)
- {
- }
- else
- {
- TRACE("Abort(): Unable to suspend dispatcher.\n");
- sRes = -3;
- }
- }
- // If completed successfully . . .
- if (sRes == 0)
- {
- // Change state.
- if (SetState(RT_STATE_ABORTING) != 0)
- {
- TRACE("Abort(): At least one handler responded with an error.\n");
- }
- }
- }
- else
- {
- TRACE("Abort(): The stream file is already stopped!\n");
- sRes = -1;
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Pauses play.
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::Pause(void)
- {
- short sRes = 0; // Assume success.
- // If playing . . .
- if (m_usState == RT_STATE_PLAYING)
- {
- // This should suspend any timer based stuff.
- m_rttime.Suspend();
- // If completed successfully . . .
- if (sRes == 0)
- {
- // Change state.
- if (SetState(RT_STATE_PAUSE) == 0)
- {
- }
- else
- {
- TRACE("Pause(): At least one handler responded with an error to "
- "entering the PAUSE state.\n");
- sRes = -3;
- }
- }
- }
- else
- {
- TRACE("Pause(): The stream file is stopped!\n");
- sRes = -1;
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- //
- // Resumes play (after Pause()).
- // Returns 0 on success.
- //
- //////////////////////////////////////////////////////////////////////////////
- short CRtPlay::Resume(void)
- {
- short sRes = 0; // Assume success.
- if (m_usState == RT_STATE_PAUSE)
- {
- // This should resume any timer based stuff.
- m_rttime.Resume();
- // Change state.
- if (SetState(RT_STATE_PLAYING) == 0)
- {
- }
- else
- {
- TRACE("Resume(): At least one handler responded with an "
- "error to entering the PLAYING state.\n");
- sRes = -4;
- if (Abort() != 0)
- {
- TRACE("Resume(): Unable to abort after failed resume.\n");
- }
- }
- }
- else
- {
- TRACE("Resume(): The stream file is not paused!\n");
- sRes = -1;
- }
- return sRes;
- }
- //////////////////////////////////////////////////////////////////////////////
- // EOF
- //////////////////////////////////////////////////////////////////////////////
|