/*
* Copyright (c) 2002-2006 Samit Basu
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 2 of the License, or
* (at your option) any later version.
*
* 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., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
*
*/
#include <qapplication.h>
#include <QDir>
#include <QDebug>
#include <QtCore>
#include <QtGui>
#include "Common.hpp"
#include "MainApp.hpp"
#include "KeyManager.hpp"
#include "File.hpp"
#include "Module.hpp"
#include "Class.hpp"
#include "LoadCore.hpp"
#include "LoadFN.hpp"
#include "HandleCommands.hpp"
#include "Core.hpp"
#include "HandleList.hpp"
#include "Interpreter.hpp"
#include "HandleWindow.hpp"
#include "PathSearch.hpp"
HandleList<Interpreter*> m_threadHandles;
#ifdef Q_WS_X11
#include "FuncTerminal.hpp"
#include "DumbTerminal.hpp"
#include "Terminal.hpp"
#include <unistd.h>
#include <fcntl.h>
#include <qsocketnotifier.h>
#include <signal.h>
#include <unistd.h>
sig_t signal_suspend_default;
sig_t signal_resume_default;
Terminal* gterm;
void signal_suspend(int a) {
Terminal *tptr = dynamic_cast<Terminal*>(gterm);
if (tptr)
tptr->RestoreOriginalMode();
printf("Suspending FreeMat...\n");
fflush(stdout);
signal(SIGTSTP,signal_suspend_default);
raise(SIGTSTP);
}
void signal_resume(int a) {
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
printf("Resuming FreeMat...\n");
Terminal *tptr = dynamic_cast<Terminal*>(gterm);
if (tptr) {
tptr->SetRawMode();
}
}
void signal_resize(int a) {
Terminal *tptr = dynamic_cast<Terminal*>(gterm);
if (tptr) {
tptr->ResizeEvent();
}
}
#endif
MainApp::MainApp() {
guimode = true;
GUIHack = false;
skipGreeting = false;
m_keys = new KeyManager;
m_global = new Scope("global",false);
// The global scope is special
m_global->mutexSetup();
}
MainApp::~MainApp() {
}
void MainApp::HelpWin() {
ArrayVector dummy;
HelpWinFunction(0,dummy,m_eval);
}
void MainApp::SetupGUICase() {
if (inBundleMode()) {
QDir dir(QApplication::applicationDirPath());
dir.cdUp();
dir.cd("Plugins");
QString dummy(dir.absolutePath());
QApplication::setLibraryPaths(QStringList() << dir.absolutePath());
}
m_win = new ApplicationWindow;
QTTerm *gui = new QTTerm;
m_keys->RegisterTerm(gui);
m_win->SetGUITerminal(gui);
m_win->SetKeyManager(m_keys);
m_win->readSettings();
m_win->show();
gui->setFocus();
QObject::connect(m_win,SIGNAL(startHelp()),this,SLOT(HelpWin()));
QObject::connect(m_win,SIGNAL(startEditor()),this,SLOT(Editor()));
QObject::connect(m_win,SIGNAL(startPathTool()),this,SLOT(PathTool()));
QObject::connect(qApp,SIGNAL(aboutToQuit()),m_win,SLOT(writeSettings()));
QObject::connect(qApp,SIGNAL(lastWindowClosed()),qApp,SLOT(quit()));
QObject::connect(this,SIGNAL(Shutdown()),m_win,SLOT(close()));
QObject::connect(this,SIGNAL(Initialize()),m_win,SLOT(init()));
m_term = gui;
}
void MainApp::SetupInteractiveTerminalCase() {
#ifdef Q_WS_X11
GUIHack = true;
Terminal *myterm = new Terminal;
gterm = myterm;
m_keys->RegisterTerm(myterm);
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
try {
myterm->Initialize();
} catch(Exception &e) {
fprintf(stderr,"Unable to initialize terminal. Try to start FreeMat with the '-e' option.");
exit(1);
}
QSocketNotifier *notify = new QSocketNotifier(STDIN_FILENO,QSocketNotifier::Read);
QObject::connect(notify, SIGNAL(activated(int)), myterm, SLOT(DoRead()));
myterm->ResizeEvent();
signal_suspend_default = signal(SIGTSTP,signal_suspend);
signal_resume_default = signal(SIGCONT,signal_resume);
signal(SIGWINCH, signal_resize);
m_term = myterm;
QObject::connect(this,SIGNAL(Shutdown()),qApp,SLOT(quit()));
#endif
}
KeyManager* MainApp::GetKeyManager() {
return m_keys;
}
void MainApp::SetupDumbTerminalCase() {
#ifdef Q_WS_X11
GUIHack = true;
DumbTerminal *myterm = new DumbTerminal;
m_keys->RegisterTerm(myterm);
fcntl(STDIN_FILENO, F_SETFL, fcntl(STDIN_FILENO, F_GETFL) | O_NONBLOCK);
QSocketNotifier *notify = new QSocketNotifier(STDIN_FILENO,QSocketNotifier::Read);
QObject::connect(notify, SIGNAL(activated(int)), myterm, SLOT(DoRead()));
signal_suspend_default = signal(SIGTSTP,signal_suspend);
signal_resume_default = signal(SIGCONT,signal_resume);
signal(SIGWINCH, signal_resize);
m_term = myterm;
QObject::connect(this,SIGNAL(Shutdown()),qApp,SLOT(quit()));
#endif
}
void MainApp::PathTool() {
ArrayVector dummy;
PathToolFunction(0,dummy,m_eval);
}
extern MainApp *m_app;
//!
//@Module EDITOR Open Editor Window
//@@Section FREEMAT
//@@Usage
//Brings up the editor window. The @|editor| function takes no
//arguments:
//@[
// editor
//@]
//!
ArrayVector EditorFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
static FMEditor *edit = NULL;
if (edit == NULL) {
edit = new FMEditor(eval);
QObject::connect(eval,SIGNAL(RefreshBPLists()),edit,SLOT(RefreshBPLists()));
QObject::connect(eval,SIGNAL(ShowActiveLine()),edit,SLOT(ShowActiveLine()));
// Because of the threading setup, we need the keymanager to relay commands
// from the editor to the interpreter.
QObject::connect(edit,SIGNAL(EvaluateText(QString)),m_app->GetKeyManager(),SLOT(QueueMultiString(QString)));
}
edit->showNormal();
edit->raise();
return ArrayVector();
}
//!
//@Module EDIT Open Editor Window
//@@Section FREEMAT
//@@Usage
//Brings up the editor window. The arguments of @|edit| function
//are names of files for editing:
//@[
// edit file1 file2 file3
//@]
//!
ArrayVector EditFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
static FMEditor *edit= NULL;
//Open the editor
if (edit == NULL) {
edit = new FMEditor(eval);
QObject::connect(eval, SIGNAL(RefreshBPLists()), edit,
SLOT(RefreshBPLists()));
QObject::connect(eval, SIGNAL(ShowActiveLine()), edit,
SLOT(ShowActiveLine()));
// Because of the threading setup, we need the keymanager to relay commands
// from the editor to the interpreter.
QObject::connect(edit, SIGNAL(EvaluateText(QString)),
m_app->GetKeyManager(), SLOT(QueueMultiString(QString)));
}
if (edit ) {
//Load files listed in the command line
for (int i=0; i<arg.size(); ++i ) {
if (arg[i].isString()) {
string fname = arg[i].getContentsAsString();
FuncPtr val;
bool isFun = eval->getContext()->lookupFunction(fname,val);
if (isFun && (val->type() == FM_M_FUNCTION)) {
//if file is a matlab file get the file name from the interpreter
MFunctionDef* mfun = (MFunctionDef*)val;
if( mfun )
fname = mfun->fileName;
}
else {
//otherwise try to find it in the path
PathSearcher psearch(eval->getTotalPath());
fname = psearch.ResolvePath( fname );
}
edit->loadFile(QString::fromStdString(fname));
} else {
throw Exception("Illegal file name");
}
}
}
edit->showNormal();
edit->raise();
return ArrayVector();
}
void MainApp::Editor() {
ArrayVector dummy;
EditorFunction(0,dummy,m_eval);
}
void MainApp::SetGUIMode(bool mode) {
guimode = mode;
}
void MainApp::SetSkipGreeting(bool skip) {
skipGreeting = skip;
}
void MainApp::Crashed() {
TerminalReset();
if (guimode)
QMessageBox::critical(NULL,"FreeMat Crash","Interpreter thread crashed unexpectedly!\n This is likely a FreeMat bug of some kind. \nPlease file a bug report at http://freemat.sf.net.",QMessageBox::Ok,QMessageBox::NoButton,QMessageBox::NoButton);
else
cout << "Interpreter thread crashed unexpectedly! This is likely a FreeMat bug of some kind. Please file a bug report at http://freemat.sf.net.";
qApp->quit();
}
void MainApp::Quit() {
TerminalReset();
m_keys->WriteHistory();
qApp->closeAllWindows();
qApp->quit();
}
void MainApp::TerminalReset() {
#ifdef Q_WS_X11
Terminal *tptr = dynamic_cast<Terminal*>(gterm);
if (tptr)
tptr->RestoreOriginalMode();
#endif
}
void MainApp::UpdateTermWidth(int w) {
m_eval->setTerminalWidth(w);
}
void MainApp::ExecuteLine(string txt) {
m_eval->ExecuteLine(txt);
}
static bool NonGUIModeHack = false;
class NonClosable : public QWidget {
public:
NonClosable() : QWidget(0,Qt::FramelessWindowHint) {};
void closeEvent(QCloseEvent *ce) {ce->ignore();}
};
static NonClosable *wid = NULL;
void MainApp::CheckNonClosable() {
if (GUIHack && !wid) {
wid = new NonClosable;
wid->setGeometry(0,0,1,1);
wid->setWindowIcon(QIcon(":/images/freemat_small_mod_64.png"));
wid->setWindowTitle("FreeMat v" VERSION);
wid->show();
}
}
void MainApp::DoGraphicsCall(Interpreter* interp, FuncPtr f, ArrayVector m, int narg) {
CheckNonClosable();
try {
ArrayVector n(f->evaluateFunction(interp,m,narg));
interp->RegisterGfxResults(n);
} catch (Exception& e) {
interp->RegisterGfxError(e.getMessageCopy());
}
}
//!
//@Module THREADID Get Current Thread Handle
//@@Section THREAD
//@@Usage
//The @|threadid| function in FreeMat tells you which thread
//is executing the context you are in. Normally, this is thread
//1, the main thread. However, if you start a new thread using
//@|threadnew|, you will be operating in a new thread, and functions
//that call @|threadid| from the new thread will return their
//handles.
//@@Example
//From the main thread, we have
//@<
//threadid
//@>
//But from a launched auxilliary thread, we have
//@<
//t_id = threadnew
//id = threadcall(t_id,1000,'threadid')
//threadfree(t_id);
//@>
//!
ArrayVector ThreadIDFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
return ArrayVector() << Array::uint32Constructor(eval->getThreadID());
}
//!
//@Module PAUSE Pause Script Execution
//@@Section IO
//@@Usage
//The @|pause| function can be used to pause execution of FreeMat
//scripts. There are several syntaxes for its use. The first form
//is
//@[
// pause
//@]
//This form of the @|pause| function pauses FreeMat until you press
//any key. The second form of the @|pause| function takes an argument
//@[
// pause(p)
//@]
//where @|p| is the number of seconds to pause FreeMat for. The pause
//argument should be accurate to a millisecond on all supported platforms.
//Alternately, you can control all @|pause| statements using:
//@[
// pause on
//@]
//which enables pauses and
//@[
// pause off
//@]
//which disables all @|pause| statements, both with and without arguments.
//!
static bool pause_active = true;
ArrayVector PauseFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
if (arg.size() == 1) {
// Check for the first argument being a string
if (arg[0].isString()) {
string parg(arg[0].getContentsAsStringUpper());
if (parg == "ON")
pause_active = true;
else if (parg == "OFF")
pause_active = false;
else
throw Exception("Unrecognized argument to pause function - must be either 'on' or 'off'");
}
if (pause_active)
eval->sleepMilliseconds((unsigned long)(ArrayToDouble(arg[0])*1000));
} else {
// Do something...
if (pause_active)
m_app->GetKeyManager()->getKeyPress();
}
return ArrayVector();
}
//!
//@Module SLEEP Sleep For Specified Number of Seconds
//@@Section FREEMAT
//@@Usage
//Suspends execution of FreeMat for the specified number
//of seconds. The general syntax for its use is
//@[
// sleep(n),
//@]
//where @|n| is the number of seconds to wait.
//!
ArrayVector SleepFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
if (arg.size() != 1)
throw Exception("sleep function requires 1 argument");
int sleeptime;
Array a(arg[0]);
sleeptime = a.getContentsAsIntegerScalar();
eval->sleepMilliseconds(1000*sleeptime);
return ArrayVector();
}
//!
//@Module THREADNEW Create a New Thread
//@@Section THREAD
//@@Usage
//The @|threadnew| function creates a new FreeMat thread, and
//returns a handle to the resulting thread. The @|threadnew|
//function takes no arguments. They general syntax for the
//@|threadnew| function is
//@[
// handle = threadnew
//@]
//Once you have a handle to a thread, you can start the thread
//on a computation using the @|threadstart| function. The
//threads returned by @|threadnew| are in a dormant state (i.e.,
//not running). Once you are finished with the thread you
//must call @|threadfree| to free the resources associated with
//that thread.
//
//Some additional important information. Thread functions operate
//in their own context or workspace, which means that data cannot
//be shared between threads. The exception is @|global| variables,
//which provide a thread-safe way for multiple threads to share data.
//Accesses to global variables are serialized so that they can
//be used to share data. Threads and FreeMat are a new feature, so
//there is room for improvement in the API and behavior. The best
//way to improve threads is to experiment with them, and send feedback.
//!
ArrayVector ThreadNewFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
// Create a new thread
int threadID = m_app->StartNewInterpreterThread();
// Translate the path from the starter thread to the new thread
Interpreter* thread = m_threadHandles.lookupHandle(threadID);
thread->setPath(eval->getPath());
return ArrayVector() << Array::uint32Constructor(threadID);
}
//!
//@Module THREADSTART Start a New Thread Computation
//@@Section THREAD
//@@Usage
//The @|threadstart| function starts a new computation on a
//FreeMat thread, and you must provide a function (no scripts
//are allowed) to run inside the thread, pass any parameters that
//the thread function requires, as well as the number of output
//arguments expected. The general syntax for the
//@|threadstart| function is
//@[
// threadstart(threadid,function,nargout,arg1,arg2,...)
//@]
//where @|threadid| is a thread handle (returned by @|threadnew|),
//where @|function| is a valid function name (it can be a built-in
//imported or M-function), @|nargout| is the number of output arguments
//expected from the function, and @|arg1| is the first argument that
//is passed to the function. Because the function runs in its
//own thread, the return values of the function are not available
//imediately. Instead, execution of that function will continue
//in parallel with the current thread. To retrieve the output
//of the thread function, you must wait for the thread to complete
//using the @|threadwait| function, and then call @|threadvalue|
// to retrieve the result. You can also stop the running thread
//prematurely by using the @|threadkill| function. It is important
//to call @|threadfree| on the handle you get from @|threadnew|
//when you are finished with the thread to ensure that the resoures
//are properly freed.
//
//It is also perfectly reasonable to use a single thread multiple
//times, calling @|threadstart| and @|threadreturn| multiple times
//on a single thread. The context is preserved between threads.
//When calling @|threadstart| on a pre-existing thread, FreeMat
//will attempt to wait on the thread. If the wait fails, then
//an error will occur.
//
//Some additional important information. Thread functions operate
//in their own context or workspace, which means that data cannot
//be shared between threads. The exception is @|global| variables,
//which provide a thread-safe way for multiple threads to share data.
//Accesses to global variables are serialized so that they can
//be used to share data. Threads and FreeMat are a new feature, so
//there is room for improvement in the API and behavior. The best
//way to improve threads is to experiment with them, and send feedback.
//
//@@Example
//Here we do something very simple. We want to obtain a listing of
//all files on the system, but do not want the results to stop our
//computation. So we run the @|system| call in a thread.
//@<
//a = threadnew; % Create the thread
//threadstart(a,'system',1,'ls -lrt /'); % Start the thread
//b = rand(100)\rand(100,1); % Solve some equations simultaneously
//c = threadvalue(a); % Retrieve the file list
//size(c) % It is large!
//threadfree(a);
//@>
//The possibilities for threads are significant. For example,
//we can solve equations in parallel, or take Fast Fourier Transforms
//on multiple threads. On multi-processor machines or multicore CPUs,
//these threaded calculations will execute in parallel. Neat.
//
//The reason for the @|nargout| argument is best illustrated with
//an example. Suppose we want to compute the Singular Value
//Decomposition @|svd| of a matrix @|A| in a thread.
//The documentation for the @|svd| function tells us that
//the behavior depends on the number of output arguments we request.
//For example, if we want a full decomposition, including the left
//and right singular vectors, and a diagonal singular matrix, we
//need to use the three-output syntax, instead of the single output
//syntax (which returns only the singular values in a column vector):
//@<
//A = float(rand(4))
//[u,s,v] = svd(A) % Compute the full decomposition
//sigmas = svd(A) % Only want the singular values
//@>
//
//Normally, FreeMat uses the left hand side of an assignment to calculate
//the number of outputs for the function. When running a function in a
//thread, we separate the assignment of the output from the invokation
//of the function. Hence, we have to provide the number of arguments at the
//time we invoke the function. For example, to compute a full decomposition
//in a thread, we specify that we want 3 output arguments:
//@<
//a = threadnew; % Create the thread
//threadstart(a,'svd',3,A); % Start a full decomposition
//[u1,s1,v1] = threadvalue(a); % Retrieve the function values
//threadfree(a);
//@>
//If we want to compute just the singular values, we start the thread
//function with only one output argument:
//@<
//a = threadnew;
//threadstart(a,'svd',1,A);
//sigmas = threadvalue(a);
//threadfree(a)
//@>
//
//!
ArrayVector ThreadStartFunction(int nargout, const ArrayVector& arg, Interpreter* eval) {
if (arg.size() < 3) throw Exception("threadstart requires at least three arguments (the thread id, the function to spawn, and the number of output arguments)");
int32 handle = ArrayToInt32(arg[0]);
unsigned long timeout = ULONG_MAX;
Interpreter* thread = m_threadHandles.lookupHandle(handle);
if (!thread) throw Exception("invalid thread handle");
string fnc = ArrayToString(arg[1]);
// Lookup this function in base interpreter to see if it is defined
FuncPtr val;
if (!eval->lookupFunction(fnc, val))
throw Exception(string("Unable to map ") + fnc + " to a defined function ");
val->updateCode(eval);
// if (val->scriptFlag)
// throw Exception(string("Cannot use a script as the main function in a thread."));
int tnargout = ArrayToInt32(arg[2]);
if (!thread->wait(1)) throw Exception("thread was busy");
ArrayVector args(arg);
args.pop_front();
args.pop_front();
args.pop_front();
thread->setThreadFunc(val,tnargout,args);
thread->start();
return ArrayVector();
}
//!
//@Module THREADVALUE Retrieve the return values from a thread
//@@Section THREAD
//@@Usage
//The @|threadvalue| function retrieves the values returned
//by the function specified in the @|threadnew| call. The
//syntax for its use is
//@[
// [arg1,arg2,...,argN] = threadvalue(handle)
//@]
//where @|handle| is the value returned by a @|threadnew| call.
//Note that there are issues with @|nargout|. See the examples
//section of @|threadnew| for details on how to work around this
//limitation. Because the function you have spawned with @|threadnew|
//may still be executing, @|threadvalue| must first @|threadwait|
//for the function to complete before retrieving the output values.
//This wait may take an arbitrarily long time if the thread function
//is caught in an infinite loop. Hence, you can also specify
//a timeout parameter to @|threadvalue| as
//@[
// [arg1,arg2,...,argN] = threadvalue(handle,timeout)
//@]
//where the @|timeout| is specified in milliseconds. If the
//wait times out, an error is raised (that can be caught with a
//@|try| and @|catch| block.
//
//In either case, if the thread function itself caused an error
//and ceased execution abruptly, then calling @|threadvalue| will
//cause that function to raise an error, allowing you to retrieve
//the error that was caused and correct it. See the examples section
//for more information.
//@@Example
//Here we do something very simple. We want to obtain a listing of
//all files on the system, but do not want the results to stop our
//computation. So we run the @|system| call in a thread.
//@<
//a = threadnew; % Create the thread
//threadstart(a,'system',1,'ls -lrt /'); % Start the thread
//b = rand(100)\rand(100,1); % Solve some equations simultaneously
//c = threadvalue(a); % Retrieve the file list
//size(c) % It is large!
//threadfree(a);
//@>
//In this example, we force the threaded function to cause an
//exception (by calling the @|error| function as the thread
//function). When we call @|threadvalue|, we get an error, instead
//of the return value of the function
//@<1
//a = threadnew
//threadstart(a,'error',0,'Hello world!'); % Will immediately stop due to error
//c = threadvalue(a) % The error comes to us
//threadfree(a)
//@>
//Note that the error has the text @|Thread:| prepended to the message
//to help you identify that this was an error in a different thread.
//!
ArrayVector ThreadValueFunction(int nargout, const ArrayVector& arg) {
if (arg.size() < 1) throw Exception("threadvalue requires at least one argument (thread id to retrieve value from)");
int32 handle = ArrayToInt32(arg[0]);
Interpreter* thread = m_threadHandles.lookupHandle(handle);
if (!thread) throw Exception("invalid thread handle");
unsigned long timeout = ULONG_MAX;
if (arg.size() > 1)
timeout = (unsigned long) ArrayToInt32(arg[1]);
if (!thread->wait()) throw Exception("error waiting for thread to complete");
if (thread->getLastErrorState())
throw Exception("Thread: " + thread->getLastErrorString());
return thread->getThreadFuncReturn();
}
//!
//@Module THREADWAIT Wait on a thread to complete execution
//@@Section THREAD
//@@Usage
//The @|threadwait| function waits for the given thread to complete
//execution, and stops execution of the current thread (the one calling
//@|threadwait|) until the given thread completes. The syntax for its
//use is
//@[
// success = threadwait(handle)
//@]
//where @|handle| is the value returned by @|threadnew| and @|success|
//is a @|logical| vaariable that will be @|1| if the wait was successful
//or @|0| if the wait times out. By default, the wait is indefinite. It
//is better to use the following form of the function
//@[
// success = threadwait(handle,timeout)
//@]
//where @|timeout| is the amount of time (in milliseconds) for
//the @|threadwait| function to wait before a timeout occurs.
//If the @|threadwait| function succeeds, then the return
//value is a logical @|1|, and if it fails, the return value
//is a logical @|0|. Note that you can call @|threadwait| multiple
//times on a thread, and if the thread is completed, each one
//will succeed.
//@@Example
//Here we lauch the @|sleep| function in a thread with a time delay of
//10 seconds. This means that the thread function will not complete
//until 10 seconds have elapsed. When we call @|threadwait| on this
//thread with a short timeout, it fails, but not when the timeout
//is long enough to capture the end of the function call.
//@<
//a = threadnew;
//threadstart(a,'sleep',0,10); % start a thread that will sleep for 10
//threadwait(a,2000) % 2 second wait is not long enough
//threadwait(a,10000) % 10 second wait is long enough
//threadfree(a)
//@>
//!
ArrayVector ThreadWaitFunction(int nargout, const ArrayVector& arg) {
if (arg.size() < 1) throw Exception("threadwait requires at least one argument (thread id to wait on)");
int32 handle = ArrayToInt32(arg[0]);
unsigned long timeout = ULONG_MAX;
Interpreter* thread = m_threadHandles.lookupHandle(handle);
if (!thread) throw Exception("invalid thread handle");
if (arg.size() > 1)
timeout = (unsigned long) ArrayToInt32(arg[1]);
bool retval = thread->wait(timeout);
if (retval && thread->getLastErrorState())
throw Exception("Thread: " + thread->getLastErrorString());
return ArrayVector() << Array::logicalConstructor(retval);
}
//!
//@Module THREADKILL Halt execution of a thread
//@@Section THREAD
//@@Usage
//The @|threadkill| function stops (or attempts to stop) execution
//of the given thread. It works only for functions defined in M-files
//(i.e., not for built in or imported functions), and it works by
//setting a flag that causes the thread to stop execution at the next
//available statement. The syntax for this function is
//@[
// threadkill(handle)
//@]
//where @|handle| is the value returned by a @|threadnew| call.
//Note that the @|threadkill| function returns immediately. It
//is still your responsibility to call @|threadfree| to free
//the thread you have halted.
//
//You cannot kill the main thread (thread id @|1|).
//@@Example
//Here is an example of stopping a runaway thread using @|threadkill|.
//Note that the thread function in this case is an M-file function.
//We start by setting up a free running counter, where we can access
//the counter from the global variables.
//@{ freecount.m
//function freecount
// global count
// if (~exist('count')) count = 0; end % Initialize the counter
// while (1)
// count = count + 1; % Update the counter
// end
//@}
//We now launch this function in a thread, and use @|threadkill| to
//stop it:
//@<
//a = threadnew;
//global count % register the global variable count
//count = 0;
//threadstart(a,'freecount',0) % start the thread
//count % it is counting
//sleep(1) % Wait a bit
//count % it is still counting
//threadkill(a) % kill the counter
//threadwait(a,1000) % wait for it to finish
//count % The count will no longer increase
//sleep(1)
//count
//threadfree(a)
//@>
//!
ArrayVector ThreadKillFunction(int nargout, const ArrayVector& arg) {
if (arg.size() < 1) throw Exception("threadkill requires at least one argument (thread id to kill)");
int32 handle = ArrayToInt32(arg[0]);
if (handle == 1) throw Exception("threadkill cannot be used on the main thread");
Interpreter* thread = m_threadHandles.lookupHandle(handle);
thread->setKill();
return ArrayVector();
}
//!
//@Module THREADFREE Free thread resources
//@@Section THREAD
//@@Usage
//The @|threadfree| is a function to free the resources claimed
//by a thread that has finished. The syntax for its use is
//@[
// threadfree(handle)
//@]
//where @|handle| is the handle returned by the call to @|threadnew|.
//The @|threadfree| function requires that the thread be completed.
//Otherwise it will wait for the thread to complete, potentially
//for an arbitrarily long period of time. To fix this, you can
//either call @|threadfree| only on threads that are known to have
//completed, or you can call it using the syntax
//@[
// threadfree(handle,timeout)
//@]
//where @|timeout| is a time to wait in milliseconds. If the thread
//fails to complete before the timeout expires, an error occurs.
//
//!
ArrayVector ThreadFreeFunction(int nargout, const ArrayVector& arg) {
if (arg.size() < 1) throw Exception("threadfree requires at least one argument (thread id to wait on) - the optional second argument is the timeout to wait for the thread to finish");
int32 handle = ArrayToInt32(arg[0]);
Interpreter* thread = m_threadHandles.lookupHandle(handle);
thread->setKill();
unsigned long timeout = ULONG_MAX;
if (arg.size() > 1)
timeout = (unsigned long) ArrayToInt32(arg[1]);
if (!thread->wait(timeout))
throw Exception("Cannot free thread... it is still running");
delete thread;
m_threadHandles.deleteHandle(handle);
return ArrayVector();
}
//!
//@Module CLC Clear Dislplay
//@@Section FREEMAT
//@@Usage
//The @|clc| function clears the current display. The syntax for
//its use is
//@[
// clc
//@]
//!
ArrayVector ClcFunction(int nargout, const ArrayVector& arg) {
m_app->GetKeyManager()->ClearDisplayCommand();
return ArrayVector();
}
void LoadThreadFunctions(Context *context) {
context->addSpecialFunction("threadid",ThreadIDFunction,0,1,NULL);
context->addSpecialFunction("threadnew",ThreadNewFunction,0,1,NULL);
context->addSpecialFunction("threadstart",ThreadStartFunction,-1,0,NULL);
context->addFunction("threadvalue",ThreadValueFunction,2,-1,"handle","timeout",NULL);
context->addFunction("threadwait",ThreadWaitFunction,2,1,"handle","timeout",NULL);
context->addFunction("threadkill",ThreadKillFunction,1,0,"handle",NULL);
context->addFunction("threadfree",ThreadFreeFunction,2,0,"handle","timeout",NULL);
context->addGfxSpecialFunction("pause",PauseFunction,1,0,"x",NULL);
context->addGfxSpecialFunction("sleep",SleepFunction,1,0,"x",NULL);
context->addGfxFunction("clc",ClcFunction,0,0,NULL);
context->addGfxSpecialFunction("editor",EditorFunction,0,0,NULL);
context->addGfxSpecialFunction("edit",EditFunction,-1,0,NULL);
}
void MainApp::EnableRepaint() {
GfxEnableRepaint();
}
void MainApp::DisableRepaint() {
GfxDisableRepaint();
}
Context *MainApp::NewContext() {
Context *context = new Context(m_global);
LoadModuleFunctions(context);
LoadClassFunction(context);
LoadCoreFunctions(context);
LoadFNFunctions(context);
if (guimode) {
LoadGUICoreFunctions(context);
LoadHandleGraphicsFunctions(context);
}
LoadThreadFunctions(context);
return context;
}
void MainApp::UpdatePaths() {
static bool paths_set = false;
if (!paths_set) {
if (inBundleMode()) {
QDir dir1(qApp->applicationDirPath() + "/../Resources/toolbox");
if (dir1.exists()) {
QString path1(dir1.canonicalPath());
basePath += GetRecursiveDirList(path1);
}
QDir dir2(qApp->applicationDirPath() + "/../Resources/help/text");
if (dir2.exists()) {
QString path2(dir2.canonicalPath());
basePath += GetRecursiveDirList(path2);
}
} else {
QSettings settings("FreeMat","FreeMat");
QDir dir1(QString(settings.value("root", RESOURCEDIR).toString())+"/toolbox");
if (dir1.exists()) {
QString path1(dir1.canonicalPath());
basePath += GetRecursiveDirList(path1);
}
QDir dir2(QString(settings.value("root", RESOURCEDIR).toString())+"/help/text");
if (dir2.exists()) {
QString path2(dir2.canonicalPath());
basePath += GetRecursiveDirList(path2);
}
}
QSettings settings("FreeMat","FreeMat");
userPath = settings.value("interpreter/path").toStringList();
paths_set = true;
}
}
int MainApp::StartNewInterpreterThread() {
Interpreter *p_eval = new Interpreter(NewContext());
p_eval->setBasePath(basePath);
p_eval->setUserPath(userPath);
p_eval->rescanPath();
connect(p_eval,SIGNAL(outputRawText(string)),m_term,SLOT(OutputRawString(string)));
connect(p_eval,SIGNAL(SetPrompt(string)),m_keys,SLOT(SetPrompt(string)));
connect(p_eval,SIGNAL(doGraphicsCall(Interpreter*,FuncPtr,ArrayVector,int)),
this,SLOT(DoGraphicsCall(Interpreter*,FuncPtr,ArrayVector,int)));
connect(p_eval,SIGNAL(CWDChanged()),m_keys,SIGNAL(UpdateCWD()));
connect(p_eval,SIGNAL(QuitSignal()),this,SLOT(Quit()));
connect(p_eval,SIGNAL(CrashedSignal()),this,SLOT(Crashed()));
connect(p_eval,SIGNAL(EnableRepaint()),this,SLOT(EnableRepaint()));
connect(p_eval,SIGNAL(DisableRepaint()),this,SLOT(DisableRepaint()));
p_eval->setTerminalWidth(m_keys->getTerminalWidth());
p_eval->setGreetingFlag(skipGreeting);
int threadID = m_threadHandles.assignHandle(p_eval);
p_eval->setThreadID(threadID);
#ifdef __OpenBSD__
/* 64 frames / calls deep */
p_eval->setStackSize(262144);
#endif
return threadID;
}
static int m_mainID;
void MainApp::RegisterInterrupt() {
// Get the main interpreter thread
m_eval = m_threadHandles.lookupHandle(m_mainID);
if (m_eval)
m_eval->setInterrupt();
}
int MainApp::Run() {
UpdatePaths();
qRegisterMetaType<string>("string");
qRegisterMetaType<FuncPtr>("FuncPtr");
qRegisterMetaType<ArrayVector>("ArrayVector");
qRegisterMetaType<Interpreter*>("Interpreter*");
connect(m_keys,SIGNAL(ExecuteLine(string)),this,SLOT(ExecuteLine(string)));
connect(m_keys,SIGNAL(UpdateTermWidth(int)),this,SLOT(UpdateTermWidth(int)));
connect(m_keys,SIGNAL(RegisterInterrupt()),this,SLOT(RegisterInterrupt()));
// Get a new thread
GfxEnableRepaint();
m_mainID = StartNewInterpreterThread();
// Assign this to the main thread
m_eval = m_threadHandles.lookupHandle(m_mainID);
m_keys->SetCompletionContext(m_eval->getContext());
FuncPtr doCLI;
if (!m_eval->lookupFunction("docli",doCLI))
return 0;
m_eval->setThreadFunc(doCLI,0,ArrayVector());
m_eval->start();
emit Initialize();
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1