1 | /* |
---|
2 | * ORXONOX - the hottest 3D action shooter ever to exist |
---|
3 | * > www.orxonox.net < |
---|
4 | * |
---|
5 | * |
---|
6 | * License notice: |
---|
7 | * |
---|
8 | * This program is free software; you can redistribute it and/or |
---|
9 | * modify it under the terms of the GNU General Public License |
---|
10 | * as published by the Free Software Foundation; either version 2 |
---|
11 | * of the License, or (at your option) any later version. |
---|
12 | * |
---|
13 | * This program is distributed in the hope that it will be useful, |
---|
14 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
---|
15 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the |
---|
16 | * GNU General Public License for more details. |
---|
17 | * |
---|
18 | * You should have received a copy of the GNU General Public License |
---|
19 | * along with this program; if not, write to the Free Software |
---|
20 | * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. |
---|
21 | * |
---|
22 | * Author: |
---|
23 | * Fabian 'x3n' Landau |
---|
24 | * Co-authors: |
---|
25 | * ... |
---|
26 | * |
---|
27 | */ |
---|
28 | |
---|
29 | #include "TclThreadManager.h" |
---|
30 | |
---|
31 | #include <boost/bind.hpp> |
---|
32 | #include <boost/thread/thread.hpp> |
---|
33 | #include <boost/thread/locks.hpp> |
---|
34 | #include <boost/thread/shared_mutex.hpp> |
---|
35 | #include <OgreTimer.h> |
---|
36 | #include <cpptcl/cpptcl.h> |
---|
37 | |
---|
38 | #include "util/Clock.h" |
---|
39 | #include "util/Convert.h" |
---|
40 | #include "util/Exception.h" |
---|
41 | #include "util/StringUtils.h" |
---|
42 | #include "core/CoreIncludes.h" |
---|
43 | #include "CommandExecutor.h" |
---|
44 | #include "ConsoleCommand.h" |
---|
45 | #include "TclBind.h" |
---|
46 | #include "TclThreadList.h" |
---|
47 | |
---|
48 | namespace orxonox |
---|
49 | { |
---|
50 | const float TCLTHREADMANAGER_MAX_CPU_USAGE = 0.50f; |
---|
51 | |
---|
52 | SetConsoleCommand("tclexecute", &TclThreadManager::execute).argumentCompleter(0, autocompletion::tclthreads()); |
---|
53 | SetConsoleCommand("tclquery", &TclThreadManager::query ).argumentCompleter(0, autocompletion::tclthreads()); |
---|
54 | SetConsoleCommand("TclThreadManager", "create", &TclThreadManager::create); |
---|
55 | SetConsoleCommand("TclThreadManager", "destroy", &TclThreadManager::destroy).argumentCompleter(0, autocompletion::tclthreads()); |
---|
56 | SetConsoleCommand("TclThreadManager", "execute", &TclThreadManager::execute).argumentCompleter(0, autocompletion::tclthreads()); |
---|
57 | SetConsoleCommand("TclThreadManager", "query", &TclThreadManager::query ).argumentCompleter(0, autocompletion::tclthreads()); |
---|
58 | SetConsoleCommand("TclThreadManager", "source", &TclThreadManager::source ).argumentCompleter(0, autocompletion::tclthreads()); |
---|
59 | |
---|
60 | /** |
---|
61 | @brief A struct containing all information about a Tcl-interpreter |
---|
62 | */ |
---|
63 | struct TclInterpreterBundle |
---|
64 | { |
---|
65 | TclInterpreterBundle() |
---|
66 | { |
---|
67 | this->lock_ = new boost::unique_lock<boost::mutex>(this->mutex_, boost::defer_lock); |
---|
68 | this->bRunning_ = true; |
---|
69 | } |
---|
70 | |
---|
71 | ~TclInterpreterBundle() |
---|
72 | { |
---|
73 | delete this->lock_; |
---|
74 | } |
---|
75 | |
---|
76 | unsigned int id_; ///< The id of the interpreter |
---|
77 | Tcl::interpreter* interpreter_; ///< The Tcl-interpreter |
---|
78 | boost::mutex mutex_; ///< A mutex to lock the interpreter while it's being used |
---|
79 | boost::unique_lock<boost::mutex>* lock_; ///< The corresponding lock for the mutex |
---|
80 | TclThreadList<std::string> queue_; ///< The command queue for commands passed by execute(command) |
---|
81 | TclThreadList<unsigned int> queriers_; ///< A list containing the id's of all other threads sending a query to this interpreter (to avoid circular queries and deadlocks) |
---|
82 | bool bRunning_; ///< This variable stays true until destroy() gets called |
---|
83 | }; |
---|
84 | |
---|
85 | TclThreadManager* TclThreadManager::singletonPtr_s = 0; |
---|
86 | |
---|
87 | /** |
---|
88 | @brief Constructor |
---|
89 | @param interpreter A pointer to the standard Tcl-interpreter (see @ref TclBind) |
---|
90 | */ |
---|
91 | TclThreadManager::TclThreadManager(Tcl::interpreter* interpreter) |
---|
92 | { |
---|
93 | RegisterRootObject(TclThreadManager); |
---|
94 | |
---|
95 | this->numInterpreterBundles_ = 0; |
---|
96 | |
---|
97 | this->interpreterBundlesMutex_ = new boost::shared_mutex(); |
---|
98 | this->mainInterpreterMutex_ = new boost::mutex(); |
---|
99 | this->messageQueue_ = new TclThreadList<std::string>(); |
---|
100 | |
---|
101 | TclInterpreterBundle* newbundle = new TclInterpreterBundle(); |
---|
102 | newbundle->id_ = 0; |
---|
103 | newbundle->interpreter_ = interpreter; |
---|
104 | newbundle->lock_->lock(); |
---|
105 | |
---|
106 | { |
---|
107 | boost::unique_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_); |
---|
108 | this->interpreterBundles_[0] = newbundle; |
---|
109 | } |
---|
110 | } |
---|
111 | |
---|
112 | /** |
---|
113 | @brief Destructor |
---|
114 | */ |
---|
115 | TclThreadManager::~TclThreadManager() |
---|
116 | { |
---|
117 | delete this->interpreterBundlesMutex_; |
---|
118 | // delete this->mainInterpreterMutex_; // <-- temporary disabled to avoid crash if a thread is still actively queriyng |
---|
119 | delete this->messageQueue_; |
---|
120 | } |
---|
121 | |
---|
122 | /** |
---|
123 | @brief The "main loop" of the TclThreadManager. Creates new threads if needed and handles queries and queued commands for the main interpreter. |
---|
124 | */ |
---|
125 | void TclThreadManager::preUpdate(const Clock& time) |
---|
126 | { |
---|
127 | // Get the bundle of the main interpreter (0) |
---|
128 | TclInterpreterBundle* bundle = this->getInterpreterBundle(0); |
---|
129 | if (bundle) |
---|
130 | { |
---|
131 | // Unlock the mutex to allow other threads accessing the main interpreter |
---|
132 | bundle->lock_->unlock(); |
---|
133 | |
---|
134 | // Lock the main interpreter mutex once to synchronize with threads that want to query the main interpreter |
---|
135 | { |
---|
136 | boost::unique_lock<boost::mutex> lock(*this->mainInterpreterMutex_); |
---|
137 | } |
---|
138 | |
---|
139 | // Lock the mutex again to gain exclusive access to the interpreter for the rest of the mainloop |
---|
140 | bundle->lock_->lock(); |
---|
141 | |
---|
142 | // Execute commands in the queues of the threaded interpreters |
---|
143 | { |
---|
144 | boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_); |
---|
145 | for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it) |
---|
146 | { |
---|
147 | if (it->first == 0) |
---|
148 | continue; // We'll handle the default interpreter later (and without threads of course) |
---|
149 | |
---|
150 | TclInterpreterBundle* bundle = it->second; |
---|
151 | if (!bundle->queue_.empty()) |
---|
152 | { |
---|
153 | // There are commands in the queue |
---|
154 | try |
---|
155 | { |
---|
156 | if (!bundle->lock_->owns_lock() && bundle->lock_->try_lock()) |
---|
157 | { |
---|
158 | // We sucessfully obtained a lock for the interpreter |
---|
159 | std::string command; |
---|
160 | if (bundle->queue_.try_pop_front(&command)) |
---|
161 | { |
---|
162 | // Start a thread to execute the command |
---|
163 | boost::thread(boost::bind(&tclThread, bundle, command)); |
---|
164 | } |
---|
165 | else |
---|
166 | { |
---|
167 | // Somehow the queue become empty (maybe multiple consumers) - unlock the mutex |
---|
168 | bundle->lock_->unlock(); |
---|
169 | } |
---|
170 | } |
---|
171 | } |
---|
172 | catch (...) |
---|
173 | { |
---|
174 | // A lock error occurred - this is possible if the lock gets locked between !bundle->lock_->owns_lock() and bundle->lock_->try_lock() |
---|
175 | // This isn't too bad, just continue |
---|
176 | } |
---|
177 | } |
---|
178 | } |
---|
179 | } |
---|
180 | |
---|
181 | // Execute commands in the message queue |
---|
182 | if (!this->messageQueue_->empty()) |
---|
183 | { |
---|
184 | std::string command; |
---|
185 | while (true) |
---|
186 | { |
---|
187 | // Pop the front value from the list (break the loop if there are no elements in the list) |
---|
188 | if (!this->messageQueue_->try_pop_front(&command)) |
---|
189 | break; |
---|
190 | |
---|
191 | // Execute the command |
---|
192 | CommandExecutor::execute(command, false); |
---|
193 | } |
---|
194 | } |
---|
195 | |
---|
196 | // Execute commands in the queue of the main interpreter |
---|
197 | if (!bundle->queue_.empty()) |
---|
198 | { |
---|
199 | // Calculate the time we have until we reach the maximal cpu usage |
---|
200 | unsigned long maxtime = (unsigned long)(time.getDeltaTime() * 1000000 * TCLTHREADMANAGER_MAX_CPU_USAGE); |
---|
201 | |
---|
202 | Ogre::Timer timer; |
---|
203 | std::string command; |
---|
204 | |
---|
205 | while (timer.getMicroseconds() < maxtime) |
---|
206 | { |
---|
207 | // Pop the front value from the list (break the loop if there are no elements in the list) |
---|
208 | if (!bundle->queue_.try_pop_front(&command)) |
---|
209 | break; |
---|
210 | |
---|
211 | // Execute the command |
---|
212 | CommandExecutor::execute(command, false); |
---|
213 | } |
---|
214 | } |
---|
215 | } |
---|
216 | } |
---|
217 | |
---|
218 | /** |
---|
219 | @brief Creates a new Tcl-interpreter. |
---|
220 | */ |
---|
221 | unsigned int TclThreadManager::create() |
---|
222 | { |
---|
223 | TclThreadManager::getInstance().numInterpreterBundles_++; |
---|
224 | TclThreadManager::createWithId(TclThreadManager::getInstance().numInterpreterBundles_); |
---|
225 | COUT(0) << "Created new Tcl-interpreter with ID " << TclThreadManager::getInstance().numInterpreterBundles_ << std::endl; |
---|
226 | return TclThreadManager::getInstance().numInterpreterBundles_; |
---|
227 | } |
---|
228 | |
---|
229 | /** |
---|
230 | @brief Creates a new Tcl-interpreter with a given id. |
---|
231 | |
---|
232 | Use with caution - if the id collides with an already existing interpreter, this call will fail. |
---|
233 | This will also be a problem, if the auto-numbered interpreters (by using create()) reach an id |
---|
234 | which was previously used in this function. Use high numbers to be safe. |
---|
235 | */ |
---|
236 | Tcl::interpreter* TclThreadManager::createWithId(unsigned int id) |
---|
237 | { |
---|
238 | TclInterpreterBundle* newbundle = new TclInterpreterBundle(); |
---|
239 | newbundle->id_ = id; |
---|
240 | newbundle->interpreter_ = TclBind::createTclInterpreter(); |
---|
241 | |
---|
242 | TclThreadManager::initialize(newbundle); |
---|
243 | |
---|
244 | { |
---|
245 | // Add the new bundle to the map |
---|
246 | boost::unique_lock<boost::shared_mutex> lock(*TclThreadManager::getInstance().interpreterBundlesMutex_); |
---|
247 | TclThreadManager::getInstance().interpreterBundles_[id] = newbundle; |
---|
248 | } |
---|
249 | |
---|
250 | return newbundle->interpreter_; |
---|
251 | } |
---|
252 | |
---|
253 | void TclThreadManager::initialize(TclInterpreterBundle* bundle) |
---|
254 | { |
---|
255 | const std::string& id_string = getConvertedValue<unsigned int, std::string>(bundle->id_); |
---|
256 | |
---|
257 | // Initialize the new interpreter |
---|
258 | try |
---|
259 | { |
---|
260 | // Define the functions which are implemented in C++ |
---|
261 | bundle->interpreter_->def("::orxonox::execute", TclThreadManager::tcl_execute, Tcl::variadic()); |
---|
262 | bundle->interpreter_->def("::orxonox::crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic()); |
---|
263 | bundle->interpreter_->def("::orxonox::query", TclThreadManager::tcl_query, Tcl::variadic()); |
---|
264 | bundle->interpreter_->def("::orxonox::crossquery", TclThreadManager::tcl_crossquery, Tcl::variadic()); |
---|
265 | bundle->interpreter_->def("::orxonox::running", TclThreadManager::tcl_running); |
---|
266 | |
---|
267 | // Create threadspecific shortcuts for the functions above |
---|
268 | bundle->interpreter_->def("execute", TclThreadManager::tcl_execute, Tcl::variadic()); |
---|
269 | bundle->interpreter_->def("crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic()); |
---|
270 | bundle->interpreter_->eval("proc query {args} { ::orxonox::query " + id_string + " $args }"); |
---|
271 | bundle->interpreter_->eval("proc crossquery {id args} { ::orxonox::crossquery " + id_string + " $id $args }"); |
---|
272 | bundle->interpreter_->eval("proc running {} { return [::orxonox::running " + id_string + "] }"); |
---|
273 | |
---|
274 | // Define a variable containing the thread id |
---|
275 | bundle->interpreter_->eval("set id " + id_string); |
---|
276 | |
---|
277 | // Use our own exit function to avoid shutting down the whole program instead of just the interpreter |
---|
278 | bundle->interpreter_->eval("rename exit ::tcl::exit"); |
---|
279 | bundle->interpreter_->eval("proc exit {} { execute TclThreadManager destroy " + id_string + " }"); |
---|
280 | |
---|
281 | // Redefine some native functions |
---|
282 | bundle->interpreter_->eval("rename while ::tcl::while"); |
---|
283 | bundle->interpreter_->eval("rename ::orxonox::while while"); |
---|
284 | bundle->interpreter_->eval("rename for ::tcl::for"); |
---|
285 | bundle->interpreter_->eval("rename ::orxonox::for for"); |
---|
286 | } |
---|
287 | catch (const Tcl::tcl_error& e) |
---|
288 | { bundle->interpreter_ = 0; COUT(1) << "Tcl error while creating Tcl-interpreter (" << id_string << "): " << e.what() << std::endl; } |
---|
289 | } |
---|
290 | |
---|
291 | /** |
---|
292 | @brief Stops and destroys a given Tcl-interpreter |
---|
293 | */ |
---|
294 | void TclThreadManager::destroy(unsigned int id) |
---|
295 | { |
---|
296 | // TODO |
---|
297 | // Not yet implemented |
---|
298 | TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(id); |
---|
299 | if (bundle) |
---|
300 | { |
---|
301 | bundle->bRunning_ = false; |
---|
302 | } |
---|
303 | } |
---|
304 | |
---|
305 | /** |
---|
306 | @brief Sends a command to the queue of a given Tcl-interpreter |
---|
307 | @param id The id of the target interpreter |
---|
308 | @param command The command to be sent |
---|
309 | */ |
---|
310 | void TclThreadManager::execute(unsigned int target_id, const std::string& command) |
---|
311 | { |
---|
312 | TclThreadManager::getInstance()._execute(target_id, command); |
---|
313 | } |
---|
314 | |
---|
315 | /** |
---|
316 | @brief This function can be called from Tcl to execute a console command. |
---|
317 | |
---|
318 | Commands which shall be executed are put into a queue and processed as soon as the |
---|
319 | main thread feels ready to do so. The queue may also be full which results in a temporary |
---|
320 | suspension of the calling thread until the queue gets ready again. |
---|
321 | */ |
---|
322 | void TclThreadManager::tcl_execute(const Tcl::object& args) |
---|
323 | { |
---|
324 | TclThreadManager::getInstance()._execute(0, stripEnclosingBraces(args.get())); |
---|
325 | } |
---|
326 | |
---|
327 | /** |
---|
328 | @brief This function can be called from Tcl to send a command to the queue of any interpreter. |
---|
329 | @param target_id The id of the target thread |
---|
330 | */ |
---|
331 | void TclThreadManager::tcl_crossexecute(int target_id, const Tcl::object& args) |
---|
332 | { |
---|
333 | TclThreadManager::getInstance()._execute(static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get())); |
---|
334 | } |
---|
335 | |
---|
336 | /** |
---|
337 | @brief Sends a command to the queue of a given Tcl-interpreter |
---|
338 | @param id The id of the target interpreter |
---|
339 | @param command The command to be sent |
---|
340 | */ |
---|
341 | void TclThreadManager::_execute(unsigned int target_id, const std::string& command) |
---|
342 | { |
---|
343 | TclInterpreterBundle* bundle = this->getInterpreterBundle(target_id); |
---|
344 | if (bundle) |
---|
345 | bundle->queue_.push_back(command); |
---|
346 | } |
---|
347 | |
---|
348 | |
---|
349 | /** |
---|
350 | @brief Sends a query to a given Tcl-interpreter and waits for the result |
---|
351 | @param id The id of the target interpreter |
---|
352 | @param command The command to be sent |
---|
353 | @return The result of the command |
---|
354 | */ |
---|
355 | std::string TclThreadManager::query(unsigned int target_id, const std::string& command) |
---|
356 | { |
---|
357 | return TclThreadManager::getInstance()._query(0, target_id, command); |
---|
358 | } |
---|
359 | |
---|
360 | /** |
---|
361 | @brief This function can be called from Tcl to send a query to the main thread. |
---|
362 | @param source_id The id of the calling thread |
---|
363 | |
---|
364 | A query waits for the result of the command. This means, the calling thread will be blocked until |
---|
365 | the main thread answers the query. In return, the main thread sends the result of the console |
---|
366 | command back to Tcl. |
---|
367 | */ |
---|
368 | std::string TclThreadManager::tcl_query(int source_id, const Tcl::object& args) |
---|
369 | { |
---|
370 | return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), 0, stripEnclosingBraces(args.get()), true); |
---|
371 | } |
---|
372 | |
---|
373 | /** |
---|
374 | @brief This function can be called from Tcl to send a query to another thread. |
---|
375 | @param source_id The id of the calling thread |
---|
376 | @param target_id The id of the target thread |
---|
377 | */ |
---|
378 | std::string TclThreadManager::tcl_crossquery(int source_id, int target_id, const Tcl::object& args) |
---|
379 | { |
---|
380 | return TclThreadManager::getInstance()._query(static_cast<unsigned int>(source_id), static_cast<unsigned int>(target_id), stripEnclosingBraces(args.get())); |
---|
381 | } |
---|
382 | |
---|
383 | /** |
---|
384 | @brief This function performs a query to any Tcl interpreter |
---|
385 | @param source_id The id of the calling thread |
---|
386 | @param target_id The id of the target thread |
---|
387 | @param command The command to send as a query |
---|
388 | @param bUseCommandExecutor Only used if the target_id is 0 (which references the main interpreter). In this case it means if the command should be passed to the CommandExecutor (true) or to the main Tcl interpreter (false). This is true when called by tcl_query and false when called by tcl_crossquery. |
---|
389 | */ |
---|
390 | std::string TclThreadManager::_query(unsigned int source_id, unsigned int target_id, const std::string& command, bool bUseCommandExecutor) |
---|
391 | { |
---|
392 | TclInterpreterBundle* source_bundle = this->getInterpreterBundle(source_id); |
---|
393 | TclInterpreterBundle* target_bundle = this->getInterpreterBundle(target_id); |
---|
394 | std::string output; |
---|
395 | |
---|
396 | if (source_bundle && target_bundle) |
---|
397 | { |
---|
398 | // At this point we assume the mutex of source_bundle to be locked (because it's executing this query right now an waits for the return value) |
---|
399 | // We can safely use it's querier list (because there's no other place in the code using the list except this query - and the interpreter can't start more than one query) |
---|
400 | |
---|
401 | if ((source_bundle->id_ == target_bundle->id_) || source_bundle->queriers_.is_in(target_bundle->id_)) |
---|
402 | { |
---|
403 | // This query would lead to a deadlock - return with an error |
---|
404 | TclThreadManager::error("Error: Circular query (" + this->dumpList(source_bundle->queriers_.getList()) + ' ' + getConvertedValue<unsigned int, std::string>(source_bundle->id_) \ |
---|
405 | + " -> " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \ |
---|
406 | + "), couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) \ |
---|
407 | + " from other interpreter with ID " + getConvertedValue<unsigned int, std::string>(source_bundle->id_) + '.'); |
---|
408 | } |
---|
409 | else |
---|
410 | { |
---|
411 | boost::unique_lock<boost::mutex> lock(target_bundle->mutex_, boost::try_to_lock); |
---|
412 | boost::unique_lock<boost::mutex> mainlock(*this->mainInterpreterMutex_, boost::defer_lock); |
---|
413 | |
---|
414 | if (!lock.owns_lock() && source_bundle->id_ != 0) |
---|
415 | { |
---|
416 | // We couldn't obtain the try_lock immediately and we're not the main interpreter - wait until the lock becomes possible (note: the main interpreter won't wait and instead returns an error - see below) |
---|
417 | if (target_bundle->id_ == 0) |
---|
418 | { |
---|
419 | // We're querying the main interpreter - use the main interpreter mutex to synchronize |
---|
420 | mainlock.lock(); |
---|
421 | lock.lock(); |
---|
422 | } |
---|
423 | else |
---|
424 | { |
---|
425 | // We're querying a threaded interpreter - no synchronization needed |
---|
426 | lock.lock(); |
---|
427 | } |
---|
428 | } |
---|
429 | |
---|
430 | if (lock.owns_lock()) |
---|
431 | { |
---|
432 | // Now the mutex of target_bundle is also locked an we can update the querier list |
---|
433 | target_bundle->queriers_.insert(target_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().begin(), source_bundle->queriers_.getList().end()); |
---|
434 | target_bundle->queriers_.push_back(source_bundle->id_); |
---|
435 | |
---|
436 | // Perform the query (note: this happens in the main thread because we need the returnvalue) |
---|
437 | if (target_bundle->id_ == 0 && bUseCommandExecutor) |
---|
438 | { |
---|
439 | // It's a query to the CommandExecutor |
---|
440 | TclThreadManager::debug("TclThread_query -> CE: " + command); |
---|
441 | int error; |
---|
442 | output = CommandExecutor::query(command, &error, false); |
---|
443 | switch (error) |
---|
444 | { |
---|
445 | case CommandExecutor::Error: TclThreadManager::error("Error: Can't execute command \"" + command + "\", command doesn't exist. (T)"); break; |
---|
446 | case CommandExecutor::Incomplete: TclThreadManager::error("Error: Can't execute command \"" + command + "\", not enough arguments given. (T)"); break; |
---|
447 | case CommandExecutor::Deactivated: TclThreadManager::error("Error: Can't execute command \"" + command + "\", command is not active. (T)"); break; |
---|
448 | case CommandExecutor::Denied: TclThreadManager::error("Error: Can't execute command \"" + command + "\", access denied. (T)"); break; |
---|
449 | } |
---|
450 | } |
---|
451 | else |
---|
452 | { |
---|
453 | // It's a query to a Tcl interpreter |
---|
454 | TclThreadManager::debug("TclThread_query: " + command); |
---|
455 | |
---|
456 | output = TclThreadManager::eval(target_bundle, command, "query"); |
---|
457 | } |
---|
458 | |
---|
459 | // Clear the queriers list of the target |
---|
460 | target_bundle->queriers_.clear(); |
---|
461 | |
---|
462 | // Unlock the mutex of the target_bundle |
---|
463 | lock.unlock(); |
---|
464 | |
---|
465 | // Finally unlock the main interpreter lock if necessary |
---|
466 | if (mainlock.owns_lock()) |
---|
467 | mainlock.unlock(); |
---|
468 | } |
---|
469 | else |
---|
470 | { |
---|
471 | // This happens if the main thread tries to query a busy interpreter |
---|
472 | // To avoid a lock of the main thread, we simply don't proceed with the query in this case |
---|
473 | TclThreadManager::error("Error: Couldn't query Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(target_bundle->id_) + ", interpreter is busy right now."); |
---|
474 | } |
---|
475 | } |
---|
476 | |
---|
477 | } |
---|
478 | |
---|
479 | return output; |
---|
480 | } |
---|
481 | |
---|
482 | /** |
---|
483 | @brief Creates a non-interactive Tcl-interpreter which executes a file. |
---|
484 | */ |
---|
485 | void TclThreadManager::source(const std::string& file) |
---|
486 | { |
---|
487 | boost::thread(boost::bind(&sourceThread, file)); |
---|
488 | } |
---|
489 | |
---|
490 | /** |
---|
491 | @brief This function can be called from Tcl to ask if the thread is still suposed to be running. |
---|
492 | @param id The id of the thread in question |
---|
493 | */ |
---|
494 | bool TclThreadManager::tcl_running(int id) |
---|
495 | { |
---|
496 | TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(static_cast<unsigned int>(id)); |
---|
497 | if (bundle) |
---|
498 | return bundle->bRunning_; |
---|
499 | else |
---|
500 | return false; |
---|
501 | } |
---|
502 | |
---|
503 | /** |
---|
504 | @brief Returns the interpreter bundle with the given id. |
---|
505 | @param id The id of the interpreter |
---|
506 | @return The interpreter or 0 if the id doesn't exist |
---|
507 | */ |
---|
508 | TclInterpreterBundle* TclThreadManager::getInterpreterBundle(unsigned int id) |
---|
509 | { |
---|
510 | boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_); |
---|
511 | |
---|
512 | std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.find(id); |
---|
513 | if (it != this->interpreterBundles_.end()) |
---|
514 | { |
---|
515 | return it->second; |
---|
516 | } |
---|
517 | else |
---|
518 | { |
---|
519 | TclThreadManager::error("Error: No Tcl-interpreter with ID " + getConvertedValue<unsigned int, std::string>(id) + " existing."); |
---|
520 | return 0; |
---|
521 | } |
---|
522 | } |
---|
523 | |
---|
524 | /** |
---|
525 | @brief Returns a string containing all elements of a unsigned-integer-list separated by spaces. |
---|
526 | */ |
---|
527 | std::string TclThreadManager::dumpList(const std::list<unsigned int>& list) |
---|
528 | { |
---|
529 | std::string output; |
---|
530 | for (std::list<unsigned int>::const_iterator it = list.begin(); it != list.end(); ++it) |
---|
531 | { |
---|
532 | if (it != list.begin()) |
---|
533 | output += ' '; |
---|
534 | |
---|
535 | output += getConvertedValue<unsigned int, std::string>(*it); |
---|
536 | } |
---|
537 | return output; |
---|
538 | } |
---|
539 | |
---|
540 | /** |
---|
541 | @brief Returns a list with the numbers of all existing Tcl-interpreters. |
---|
542 | |
---|
543 | This function is used by the auto completion function. |
---|
544 | */ |
---|
545 | std::list<unsigned int> TclThreadManager::getThreadList() const |
---|
546 | { |
---|
547 | boost::shared_lock<boost::shared_mutex> lock(*this->interpreterBundlesMutex_); |
---|
548 | |
---|
549 | std::list<unsigned int> threads; |
---|
550 | for (std::map<unsigned int, TclInterpreterBundle*>::const_iterator it = this->interpreterBundles_.begin(); it != this->interpreterBundles_.end(); ++it) |
---|
551 | if (it->first > 0 && it->first <= this->numInterpreterBundles_) // only list autonumbered interpreters (created with create()) - exclude the default interpreter 0 and all manually numbered interpreters) |
---|
552 | threads.push_back(it->first); |
---|
553 | return threads; |
---|
554 | } |
---|
555 | |
---|
556 | /** |
---|
557 | @brief A helper function to print errors in a thread safe manner. |
---|
558 | */ |
---|
559 | void TclThreadManager::error(const std::string& error) |
---|
560 | { |
---|
561 | TclThreadManager::getInstance().messageQueue_->push_back("error " + error); |
---|
562 | } |
---|
563 | |
---|
564 | /** |
---|
565 | @brief A helper function to print debug information in a thread safe manner. |
---|
566 | */ |
---|
567 | void TclThreadManager::debug(const std::string& error) |
---|
568 | { |
---|
569 | TclThreadManager::getInstance().messageQueue_->push_back("debug " + error); |
---|
570 | } |
---|
571 | |
---|
572 | /** |
---|
573 | @brief Evaluates a Tcl command without throwing exceptions (which may rise problems on certain machines). |
---|
574 | @return The Tcl return value |
---|
575 | |
---|
576 | Errors are reported through the @ref error function. |
---|
577 | */ |
---|
578 | std::string TclThreadManager::eval(TclInterpreterBundle* bundle, const std::string& command, const std::string& action) |
---|
579 | { |
---|
580 | Tcl_Interp* interpreter = bundle->interpreter_->get(); |
---|
581 | int cc = Tcl_Eval(interpreter, command.c_str()); |
---|
582 | |
---|
583 | Tcl::details::result result(interpreter); |
---|
584 | |
---|
585 | if (cc != TCL_OK) |
---|
586 | { |
---|
587 | TclThreadManager::error("Tcl error (" + action + ", ID " + getConvertedValue<unsigned int, std::string>(bundle->id_) + "): " + static_cast<std::string>(result)); |
---|
588 | return ""; |
---|
589 | } |
---|
590 | else |
---|
591 | { |
---|
592 | return result; |
---|
593 | } |
---|
594 | } |
---|
595 | |
---|
596 | //////////////// |
---|
597 | // The Thread // |
---|
598 | //////////////// |
---|
599 | |
---|
600 | /** |
---|
601 | @brief The main function of the thread. Executes a Tcl command. |
---|
602 | @param bundle The interpreter bundle containing all necessary variables |
---|
603 | @param command the Command to execute |
---|
604 | */ |
---|
605 | void tclThread(TclInterpreterBundle* bundle, const std::string& command) |
---|
606 | { |
---|
607 | TclThreadManager::debug("TclThread_execute: " + command); |
---|
608 | |
---|
609 | TclThreadManager::eval(bundle, command, "execute"); |
---|
610 | |
---|
611 | bundle->lock_->unlock(); |
---|
612 | } |
---|
613 | |
---|
614 | /** |
---|
615 | @brief The main function of a non-interactive source thread. Executes the file. |
---|
616 | @param file The name of the file that should be executed by the non-interactive interpreter. |
---|
617 | */ |
---|
618 | void sourceThread(const std::string& file) |
---|
619 | { |
---|
620 | TclThreadManager::debug("TclThread_source: " + file); |
---|
621 | |
---|
622 | // Prepare the command-line arguments |
---|
623 | const int argc = 2; |
---|
624 | char* argv[argc]; |
---|
625 | argv[0] = const_cast<char*>("tclthread"); |
---|
626 | argv[1] = const_cast<char*>(file.c_str()); |
---|
627 | |
---|
628 | // Start the Tcl-command Tcl_Main with the Tcl_OrxonoxAppInit hook |
---|
629 | Tcl_Main(argc, argv, Tcl_OrxonoxAppInit); |
---|
630 | |
---|
631 | // Tcl::object object(file); |
---|
632 | // int cc = Tcl_FSEvalFile(bundle->interpreter_->get(), object.get_object()); |
---|
633 | // Tcl::details::result result(bundle->interpreter_->get()); |
---|
634 | // if (cc != TCL_OK) |
---|
635 | // TclThreadManager::error("Tcl error (source, ID " + getConvertedValue<unsigned int, std::string>(bundle->id_) + "): " + static_cast<std::string>(result)); |
---|
636 | // |
---|
637 | // // Unlock the mutex |
---|
638 | // bundle->lock_->unlock(); |
---|
639 | } |
---|
640 | |
---|
641 | /** |
---|
642 | @brief A tcl-init hook to inject the non-interactive Tcl-interpreter into the TclThreadManager. |
---|
643 | */ |
---|
644 | int Tcl_OrxonoxAppInit(Tcl_Interp* interp) |
---|
645 | { |
---|
646 | // Create a new interpreter bundle |
---|
647 | unsigned int id = TclThreadManager::create(); |
---|
648 | TclInterpreterBundle* bundle = TclThreadManager::getInstance().getInterpreterBundle(id); |
---|
649 | |
---|
650 | // Replace the default interpreter in the bundle with the non-interactive one (passed as an argument to this function) |
---|
651 | if (bundle->interpreter_) |
---|
652 | delete bundle->interpreter_; |
---|
653 | bundle->interpreter_ = new Tcl::interpreter(interp, true); |
---|
654 | |
---|
655 | // Initialize the non-interactive interpreter (like in @ref TclBind::createTclInterpreter but exception safe) |
---|
656 | const std::string& libpath = TclBind::getTclLibraryPath(); |
---|
657 | if (!libpath.empty()) |
---|
658 | TclThreadManager::eval(bundle, "set tcl_library \"" + libpath + '"', "source"); |
---|
659 | int cc = Tcl_Init(interp); |
---|
660 | TclThreadManager::eval(bundle, "source \"" + TclBind::getInstance().getTclDataPath() + "/init.tcl\"", "source"); |
---|
661 | |
---|
662 | // Initialize the non-interactive interpreter also with the thread-specific stuff |
---|
663 | TclThreadManager::initialize(bundle); |
---|
664 | |
---|
665 | // Lock the mutex (this will be locked until the thread finishes - no chance to interact with the interpreter) |
---|
666 | bundle->lock_->lock(); |
---|
667 | |
---|
668 | // Return to Tcl_Main |
---|
669 | if (!bundle->interpreter_) |
---|
670 | return TCL_ERROR; |
---|
671 | else |
---|
672 | return cc; |
---|
673 | } |
---|
674 | } |
---|