sfxFMODProject.cpp 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493
  1. //-----------------------------------------------------------------------------
  2. // Copyright (c) 2012 GarageGames, LLC
  3. //
  4. // Permission is hereby granted, free of charge, to any person obtaining a copy
  5. // of this software and associated documentation files (the "Software"), to
  6. // deal in the Software without restriction, including without limitation the
  7. // rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
  8. // sell copies of the Software, and to permit persons to whom the Software is
  9. // furnished to do so, subject to the following conditions:
  10. //
  11. // The above copyright notice and this permission notice shall be included in
  12. // all copies or substantial portions of the Software.
  13. //
  14. // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
  15. // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
  16. // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
  17. // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
  18. // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
  19. // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
  20. // IN THE SOFTWARE.
  21. //-----------------------------------------------------------------------------
  22. #include "sfx/fmod/sfxFMODProject.h"
  23. #include "sfx/fmod/sfxFMODDevice.h"
  24. #include "sfx/fmod/sfxFMODEvent.h"
  25. #include "sfx/fmod/sfxFMODEventGroup.h"
  26. #include "sfx/sfxDescription.h"
  27. #include "core/stringTable.h"
  28. #include "core/volume.h"
  29. #include "core/util/path.h"
  30. #include "core/stream/fileStream.h"
  31. #include "core/stream/bitStream.h"
  32. #include "core/util/safeDelete.h"
  33. IMPLEMENT_CO_DATABLOCK_V1( SFXFMODProject );
  34. ConsoleDocClass( SFXFMODProject,
  35. "@brief An FMOD Designer project loaded into Torque.\n\n"
  36. "@section SFXFMODProject_resources Resource Loading\n\n"
  37. "@ingroup SFXFMOD\n"
  38. "@ingroup Datablocks"
  39. );
  40. //-----------------------------------------------------------------------------
  41. SFXFMODProject::SFXFMODProject()
  42. : mHandle( NULL ),
  43. mRootGroups( NULL )
  44. {
  45. VECTOR_SET_ASSOCIATION( mGroups );
  46. VECTOR_SET_ASSOCIATION( mEvents );
  47. SFX->getEventSignal().notify( this, &SFXFMODProject::_onSystemEvent );
  48. }
  49. //-----------------------------------------------------------------------------
  50. SFXFMODProject::~SFXFMODProject()
  51. {
  52. AssertFatal( mGroups.empty(), "SFXFMODProject::~SFXFMODProject - project still has groups attached" );
  53. AssertFatal( mEvents.empty(), "SFXFMODProject::~SFXFMODProject - project still has events attached" );
  54. if( SFX )
  55. SFX->getEventSignal().remove( this, &SFXFMODProject::_onSystemEvent );
  56. }
  57. //-----------------------------------------------------------------------------
  58. void SFXFMODProject::initPersistFields()
  59. {
  60. addGroup( "FMOD" );
  61. addField( "fileName", TypeStringFilename, Offset( mFileName, SFXFMODProject ), "The compiled .fev file from FMOD Designer." );
  62. addField( "mediaPath", TypeStringFilename, Offset( mMediaPath, SFXFMODProject ), "Path to the media files; if unset, defaults to project directory." );
  63. endGroup( "FMOD" );
  64. Parent::initPersistFields();
  65. }
  66. //-----------------------------------------------------------------------------
  67. bool SFXFMODProject::onAdd()
  68. {
  69. if( !Parent::onAdd() )
  70. return false;
  71. // If this is a non-networked datablock, load the
  72. // project data now.
  73. if( isClientOnly() && !_load() )
  74. return false;
  75. return true;
  76. }
  77. //-----------------------------------------------------------------------------
  78. void SFXFMODProject::onRemove()
  79. {
  80. Parent::onRemove();
  81. _clear();
  82. }
  83. //-----------------------------------------------------------------------------
  84. bool SFXFMODProject::preload( bool server, String& errorStr )
  85. {
  86. if( !Parent::preload( server, errorStr ) )
  87. return false;
  88. if( server )
  89. {
  90. if( mFileName.isEmpty() )
  91. {
  92. errorStr = String::ToString( "SFXFMODProject::preload - no filename set on %i (%s)",
  93. getId(), getName() );
  94. return false;
  95. }
  96. if( mGroups.empty() || mEvents.empty() )
  97. _load();
  98. release();
  99. }
  100. return true;
  101. }
  102. //-----------------------------------------------------------------------------
  103. void SFXFMODProject::packData( BitStream* stream )
  104. {
  105. Parent::packData( stream );
  106. stream->write( mFileName );
  107. stream->write( mMediaPath );
  108. }
  109. //-----------------------------------------------------------------------------
  110. void SFXFMODProject::unpackData( BitStream* stream )
  111. {
  112. Parent::unpackData( stream );
  113. stream->read( &mFileName );
  114. stream->read( &mMediaPath );
  115. }
  116. //-----------------------------------------------------------------------------
  117. void SFXFMODProject::_onSystemEvent( SFXSystemEventType event )
  118. {
  119. switch( event )
  120. {
  121. case SFXSystemEvent_DestroyDevice:
  122. // If the FMOD device is being destroyed,
  123. // release all our data.
  124. if( SFXFMODDevice::instance() )
  125. release();
  126. break;
  127. default:
  128. break;
  129. }
  130. }
  131. //-----------------------------------------------------------------------------
  132. void SFXFMODProject::_clear()
  133. {
  134. release();
  135. for( U32 i = 0; i < mGroups.size(); ++ i )
  136. if( !mGroups[ i ]->isRemoved() )
  137. mGroups[ i ]->deleteObject();
  138. mGroups.clear();
  139. mEvents.clear();
  140. mRootGroups = NULL;
  141. }
  142. //-----------------------------------------------------------------------------
  143. bool SFXFMODProject::_load()
  144. {
  145. const Torque::Path eventScriptFileName = mFileName + ".cs";
  146. const Torque::Path eventScriptFileNameDSO = eventScriptFileName + ".dso";
  147. const bool eventScriptFileExists = Torque::FS::IsFile( eventScriptFileName );
  148. const bool eventScriptFileDSOExists = Torque::FS::IsFile( eventScriptFileNameDSO );
  149. // Check if we need to (re-)generate the event script file.
  150. bool needToGenerateEventScriptFile = false;
  151. if( ( !eventScriptFileExists && !eventScriptFileDSOExists )
  152. || ( Torque::FS::CompareModifiedTimes( mFileName, eventScriptFileName ) > 0
  153. || Torque::FS::CompareModifiedTimes( mFileName, eventScriptFileNameDSO ) > 0 ) )
  154. needToGenerateEventScriptFile = true;
  155. // If we need to generate, check if we can.
  156. SFXFMODDevice* fmodDevice = SFXFMODDevice::instance();
  157. if( needToGenerateEventScriptFile && !fmodDevice )
  158. {
  159. // If we have neither FMOD nor the event scripts (even if outdated),
  160. // there's nothing we can do.
  161. if( !eventScriptFileExists && !eventScriptFileDSOExists )
  162. {
  163. Con::errorf( "SFXFMODProject::_load() - event script for '%s' does not exist and device is not FMOD; load this project under FMOD first",
  164. mFileName.c_str() );
  165. return false;
  166. }
  167. // Use the oudated versions.
  168. Con::warnf( "SFXMODProject::_load() - event script for '%s' is outdated and device is not FMOD; event data may not match .fev contents",
  169. mFileName.c_str() );
  170. needToGenerateEventScriptFile = false;
  171. }
  172. // If we don't need to regenerate, try executing the event script now.
  173. if( !needToGenerateEventScriptFile )
  174. {
  175. if( ( eventScriptFileExists || eventScriptFileDSOExists )
  176. && !Con::evaluatef( "exec( \"%s\" );", eventScriptFileName.getFullPath().c_str() ) )
  177. {
  178. Con::errorf( "SFXFMODProject::_load() - failed to execute event script for '%s'%s",
  179. mFileName.c_str(),
  180. fmodDevice != NULL ? "; trying to regenerate" : ""
  181. );
  182. if( !fmodDevice )
  183. return false;
  184. needToGenerateEventScriptFile = true;
  185. }
  186. else
  187. Con::printf( "SFXFMODProject - %s: Loaded event script", getName() );
  188. }
  189. // If we need to generate the event script file,
  190. // load the FMOD project now and then emit the file.
  191. if( needToGenerateEventScriptFile )
  192. {
  193. // Try to load the project.
  194. acquire();
  195. if( !mHandle )
  196. return false;
  197. // Get the project info.
  198. FMOD_EVENT_PROJECTINFO info;
  199. int numEvents;
  200. int numGroups;
  201. SFXFMODDevice::smFunc->FMOD_EventProject_GetInfo( mHandle, &info );
  202. SFXFMODDevice::smFunc->FMOD_EventProject_GetNumEvents( mHandle, &numEvents );
  203. SFXFMODDevice::smFunc->FMOD_EventProject_GetNumGroups( mHandle, &numGroups );
  204. Con::printf( "SFXFMODProject - %s: Loading '%s' from '%s' (index: %i, events: %i, groups: %i)",
  205. getName(), info.name, mFileName.c_str(), info.index, numEvents, numGroups );
  206. // Load the root groups.
  207. for( U32 i = 0; i < numGroups; ++ i )
  208. {
  209. FMOD_EVENTGROUP* group;
  210. if( SFXFMODDevice::smFunc->FMOD_EventProject_GetGroupByIndex( mHandle, i, true, &group ) == FMOD_OK )
  211. {
  212. SFXFMODEventGroup* object = new SFXFMODEventGroup( this, group );
  213. object->mSibling = mRootGroups;
  214. mRootGroups = object;
  215. String qualifiedName = FMODEventPathToTorqueName( object->getQualifiedName() );
  216. if( !isClientOnly() )
  217. object->assignId();
  218. object->registerObject( String::ToString( "%s_%s", getName(), qualifiedName.c_str() ) );
  219. if( isClientOnly() )
  220. Sim::getRootGroup()->addObject( object );
  221. object->_load();
  222. }
  223. }
  224. // Create the event script file.
  225. FileStream stream;
  226. if( !stream.open( eventScriptFileName.getFullPath(), Torque::FS::File::Write ) )
  227. {
  228. Con::errorf( "SFXFMODProject::_load - could not create event script file for '%s'", mFileName.c_str() );
  229. return true; // Don't treat as failure.
  230. }
  231. // Write a header.
  232. stream.writeText( String::ToString( "// This file has been auto-generated from '%s'\n", mFileName.c_str() ) );
  233. stream.writeText( "// Do not edit this file manually and do not move it away from the Designer file.\n\n" );
  234. // Write the group objects.
  235. for( U32 i = 0; i < mGroups.size(); ++ i )
  236. {
  237. mGroups[ i ]->write( stream, 0 );
  238. stream.writeText( "\n" );
  239. }
  240. // Write the event objects along with their
  241. // SFXDescriptions.
  242. for( U32 i = 0; i < mEvents.size(); ++ i )
  243. {
  244. mEvents[ i ]->getDescription()->write( stream, 0 );
  245. mEvents[ i ]->write( stream, 0 );
  246. stream.writeText( "\n" );
  247. }
  248. Con::printf( "SFXFMODProject - %s: Generated event script '%s'", getName(), eventScriptFileName.getFullPath().c_str() );
  249. }
  250. return true;
  251. }
  252. //-----------------------------------------------------------------------------
  253. void SFXFMODProject::acquire( bool recursive )
  254. {
  255. // Load the project file.
  256. if( !mHandle )
  257. {
  258. FMOD_RESULT result = SFXFMODDevice::smFunc->FMOD_EventSystem_Load(
  259. SFXFMODDevice::smEventSystem,
  260. mFileName.c_str(),
  261. ( FMOD_EVENT_LOADINFO* ) 0,
  262. &mHandle
  263. );
  264. if( result != FMOD_OK )
  265. {
  266. Con::errorf( "SFXFMODProject::acquire - could not load '%s' (%s)",
  267. mFileName.c_str(), FMODResultToString( result ).c_str() );
  268. mHandle = NULL;
  269. return;
  270. }
  271. Con::printf( "SFXFMODProject - %s: Opened project '%s'", getName(), mFileName.c_str() );
  272. // Set the media path.
  273. String mediaPath;
  274. if( !mMediaPath.isEmpty() )
  275. {
  276. mediaPath = mMediaPath;
  277. if( mediaPath[ mediaPath.length() - 1 ] != '/' )
  278. mediaPath += '/';
  279. }
  280. else
  281. {
  282. // Set to project directory.
  283. Torque::Path path = mFileName;
  284. if( path.getRoot().isEmpty() )
  285. path.setRoot( "game" );
  286. path.setFileName( "" );
  287. path.setExtension( "" );
  288. mediaPath = path.getFullPath() + '/';
  289. }
  290. SFXFMODDevice::smFunc->FMOD_EventSystem_SetMediaPath(
  291. SFXFMODDevice::smEventSystem,
  292. mediaPath.c_str()
  293. );
  294. }
  295. // Acquire the root groups.
  296. if( recursive )
  297. for( SFXFMODEventGroup* group = mRootGroups; group != NULL; group = group->mSibling )
  298. group->acquire( true );
  299. SFXFMODDevice::instance()->updateMemUsageStats();
  300. }
  301. //-----------------------------------------------------------------------------
  302. void SFXFMODProject::release()
  303. {
  304. if( !mHandle )
  305. return;
  306. Con::printf( "SFXFMODProject - %s: Closing project '%s'",
  307. getName(), mFileName.c_str() );
  308. // Clear media path.
  309. SFXFMODDevice::smFunc->FMOD_EventSystem_SetMediaPath(
  310. SFXFMODDevice::smEventSystem, "" );
  311. // Release the root groups.
  312. for( SFXFMODEventGroup* group = mRootGroups; group != NULL; group = group->mSibling )
  313. group->release();
  314. // Release the project.
  315. SFXFMODDevice::smFunc->FMOD_EventProject_Release( mHandle );
  316. mHandle = NULL;
  317. SFXFMODDevice::instance()->updateMemUsageStats();
  318. }
  319. //-----------------------------------------------------------------------------
  320. void SFXFMODProject::_addEvent( SFXFMODEvent* event )
  321. {
  322. mEvents.push_back( event );
  323. }
  324. //-----------------------------------------------------------------------------
  325. void SFXFMODProject::_addGroup( SFXFMODEventGroup* group )
  326. {
  327. mGroups.push_back( group );
  328. }
  329. //-----------------------------------------------------------------------------
  330. void SFXFMODProject::_removeEvent( SFXFMODEvent* event )
  331. {
  332. for( U32 i = 0; i < mEvents.size(); ++ i )
  333. if( mEvents[ i ] == event )
  334. {
  335. mEvents.erase( i );
  336. break;
  337. }
  338. }
  339. //-----------------------------------------------------------------------------
  340. void SFXFMODProject::_removeGroup( SFXFMODEventGroup* group )
  341. {
  342. // Remove from group array.
  343. for( U32 i = 0; i < mGroups.size(); ++ i )
  344. if( mGroups[ i ] == group )
  345. {
  346. mGroups.erase( i );
  347. break;;
  348. }
  349. // Unlink if it's a root group.
  350. if( !group->mParent )
  351. {
  352. if( group == mRootGroups )
  353. {
  354. mRootGroups = group->mSibling;
  355. group->mSibling = NULL;
  356. }
  357. else
  358. {
  359. SFXFMODEventGroup* p = mRootGroups;
  360. while( p && p->mSibling != group )
  361. p = p->mSibling;
  362. if( p )
  363. {
  364. p->mSibling = group->mSibling;
  365. group->mSibling = NULL;
  366. }
  367. }
  368. }
  369. }