//----------------------------------------------------------------------------- // Copyright (c) 2013 GarageGames, LLC // // 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, sublicense, and/or // sell copies of the Software, and to permit persons to whom the Software is // furnished to do so, subject to the following conditions: // // The above copyright notice and this permission notice shall be included in // all copies or substantial portions of the Software. // // 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. IN NO EVENT SHALL THE // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING // FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS // IN THE SOFTWARE. //----------------------------------------------------------------------------- #import #import #import #import "memory/safeDelete.h" #import "platform/threads/thread.h" #import "platform/platformSemaphore.h" #import "platform/threads/mutex.h" #import "console/console.h" //----------------------------------------------------------------------------- #pragma mark ---- Thread Class Methods ---- struct PlatformThreadData { ThreadRunFunction mRunFunc; void* mRunArg; Thread* mThread; Semaphore mGateway; // default count is 1 ThreadIdent mThreadID; }; //----------------------------------------------------------------------------- static void *ThreadRunHandler( void* arg ) { // Fetch the platform thread data. PlatformThreadData* mData = reinterpret_cast(arg); // Fetch the thread. Thread* thread = mData->mThread; mData->mThreadID = ThreadManager::getCurrentThreadId(); // Add the thread. ThreadManager::addThread(thread); // Run the thread against a pool. NSAutoreleasePool *pool = [[NSAutoreleasePool alloc] init]; thread->run(mData->mRunArg); [pool drain]; // Release the thread. mData->mGateway.release(); // Does the thread want to be auto-deleted? if(thread->autoDelete) { // Yes, so remove the thread. ThreadManager::removeThread(thread); // Delete the thread. delete thread; } // This is for pthread. return NULL; } //----------------------------------------------------------------------------- Thread::Thread(ThreadRunFunction func, void* arg, bool start_thread, bool autodelete) { mData = new PlatformThreadData; mData->mRunFunc = func; mData->mRunArg = arg; mData->mThread = this; mData->mThreadID = 0; autoDelete = autodelete; if(start_thread) start(); } //----------------------------------------------------------------------------- Thread::~Thread() { stop(); join(); SAFE_DELETE(mData); } //----------------------------------------------------------------------------- void Thread::start() { if(isAlive()) return; // cause start to block out other pthreads from using this Thread, // at least until ThreadRunHandler exits. mData->mGateway.acquire(); // reset the shouldStop flag, so we'll know when someone asks us to stop. shouldStop = false; pthread_create((pthread_t*)(&mData->mThreadID), NULL, ThreadRunHandler, mData); } //----------------------------------------------------------------------------- bool Thread::join() { if(!isAlive()) return true; // not using pthread_join here because pthread_join cannot deal // with multiple simultaneous calls. mData->mGateway.acquire(); mData->mGateway.release(); return true; } //----------------------------------------------------------------------------- void Thread::run(void* arg) { if(mData->mRunFunc) mData->mRunFunc(arg); } //----------------------------------------------------------------------------- bool Thread::isAlive() { if(mData->mThreadID == 0) return false; if( mData->mGateway.acquire(false) ) { mData->mGateway.release(); return false; // we got the lock, it aint alive. } else return true; // we could not get the lock, it must be alive. } //----------------------------------------------------------------------------- ThreadIdent Thread::getId() { return mData->mThreadID; } #pragma mark ---- ThreadManager Class Methods ---- //----------------------------------------------------------------------------- ThreadIdent ThreadManager::getCurrentThreadId() { return (ThreadIdent)pthread_self(); } //----------------------------------------------------------------------------- bool ThreadManager::compare( ThreadIdent threadId_1, ThreadIdent threadId_2 ) { return (bool)pthread_equal((pthread_t)threadId_1, (pthread_t)threadId_2); } class ExecuteThread : public Thread { const char* zargs; const char* directory; const char* executable; public: ExecuteThread(const char *_executable, const char *_args /* = NULL */, const char *_directory /* = NULL */) : Thread(0, NULL, false, true) { zargs = dStrdup(_args); directory = dStrdup(_directory); executable = dStrdup(_executable); start(); } static U32 runNoThread(const char *_executable, const char *_args /* = NULL */, const char *_directory /* = NULL */); virtual void run(void* arg); }; static char* _unDoubleQuote(char* arg) { U32 len = dStrlen(arg); if(!len) return arg; if(arg[0] == '"' && arg[len-1] == '"') { arg[len - 1] = '\0'; return arg + 1; } return arg; } // this is an externably callable blocking shellExecute!!! U32 ExecuteThread::runNoThread( const char* executable, const char* zargs, const char* directory ) { printf("creating nstask\n"); NSTask *aTask = [[NSTask alloc] init]; NSMutableArray *array = [NSMutableArray array]; // scan the args list, breaking it up, space delimited, backslash escaped. U32 len = dStrlen(zargs); char args[len+1]; dStrncpy(args, zargs, len+1); char *lastarg = args; bool escaping = false; for(int i = 0; i< len; i++) { char c = args[i]; // a backslash escapes the next character if(escaping) continue; if(c == '\\') escaping = true; if(c == ' ') { args[i] = '\0'; if(*lastarg) [array addObject:[NSString stringWithUTF8String: _unDoubleQuote(lastarg)]]; lastarg = args + i + 1; } } if(*lastarg) [array addObject:[NSString stringWithUTF8String: _unDoubleQuote(lastarg)]]; [aTask setArguments: array]; [aTask setCurrentDirectoryPath:[NSString stringWithUTF8String: directory]]; [aTask setLaunchPath:[NSString stringWithUTF8String:executable]]; [aTask launch]; [aTask waitUntilExit]; U32 ret = [aTask terminationStatus]; return ret; } void ExecuteThread::run(void* arg) { // call the common run, but since we're in a thread, this won't block other processes U32 ret = runNoThread( this->executable, this->zargs, this->directory ); Con::executef(2, "onExecuteDone", Con::getIntArg(ret)); printf("done nstask\n"); } ConsoleFunction(shellExecute, bool, 2, 4, "(executable, [args], [directory])") { ExecuteThread *et; et = new ExecuteThread(argv[1], argc > 2 ? argv[2] : NULL, argc > 3 ? argv[3] : NULL); return true; // Bug: BPNC error: need feedback on whether the command was sucessful } ConsoleFunction(shellExecuteBlocking, int, 2, 4, "(executable, [args], [directory])") { return (int)ExecuteThread::runNoThread( argv[1], argc > 2 ? argv[2] : NULL, argc > 3 ? argv[3] : NULL ); }