Planet
navi homePPSaboutscreenshotsdownloaddevelopmentforum

source: code/trunk/src/libraries/core/command/TclBind.cc @ 9550

Last change on this file since 9550 was 9550, checked in by landauf, 11 years ago

merged testing branch back to trunk. unbelievable it took me 13 months to finish this chore…

  • Property svn:eol-style set to native
File size: 8.7 KB
Line 
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 "TclBind.h"
30
31#include <exception>
32#include <string>
33#include <cpptcl/cpptcl.h>
34
35#include "SpecialConfig.h"
36#include "util/Output.h"
37#include "util/Exception.h"
38#include "util/StringUtils.h"
39#include "core/PathConfig.h"
40#include "CommandExecutor.h"
41#include "ConsoleCommand.h"
42#include "TclThreadManager.h"
43
44namespace orxonox
45{
46    SetConsoleCommand("tcl", &TclBind::tcl);
47    SetConsoleCommand("bgerror", &TclBind::bgerror).hide();
48
49    TclBind* TclBind::singletonPtr_s = 0;
50
51    /**
52        @brief Constructor: Initializes the Tcl-interpreter with a given data path.
53        @param datapath Path to the directory that contains the Orxonox-specific Tcl-files
54    */
55    TclBind::TclBind(const std::string& datapath)
56    {
57        this->interpreter_ = 0;
58        this->bSetTclDataPath_ = false;
59        this->setDataPath(datapath);
60    }
61
62    /**
63        @brief Destructor: Deletes the Tcl-interpreter.
64    */
65    TclBind::~TclBind()
66    {
67        if (this->interpreter_)
68            delete this->interpreter_;
69    }
70
71    /**
72        @brief Defines the path to the directory that contains the Orxonox-specific Tcl-files and initializes the Tcl-interpreter accordingly.
73    */
74    void TclBind::setDataPath(const std::string& datapath)
75    {
76        // String has POSIX slashes
77        this->tclDataPath_ = datapath + "tcl" + '/';
78        this->bSetTclDataPath_ = true;
79
80        this->initializeTclInterpreter();
81    }
82
83    /**
84        @brief Creates and initializes the Tcl-interpreter by registering all callbacks and defining some useful functions.
85    */
86    void TclBind::initializeTclInterpreter()
87    {
88        if (this->bSetTclDataPath_ && !this->interpreter_)
89        {
90            this->interpreter_ = this->createTclInterpreter();
91
92            this->interpreter_->def("::orxonox::query", TclBind::tcl_query, Tcl::variadic());
93            this->interpreter_->def("::orxonox::execute", TclBind::tcl_execute, Tcl::variadic());
94            this->interpreter_->def("::orxonox::crossquery", TclThreadManager::tcl_crossquery, Tcl::variadic());
95            this->interpreter_->def("::orxonox::crossexecute", TclThreadManager::tcl_crossexecute, Tcl::variadic());
96
97            try
98            {
99                this->interpreter_->def("query", TclBind::tcl_query, Tcl::variadic());
100                this->interpreter_->def("execute", TclBind::tcl_execute, Tcl::variadic());
101                this->interpreter_->eval("proc crossquery   {id args} { ::orxonox::crossquery 0 $id $args }");
102                this->interpreter_->eval("proc crossexecute {id args} { ::orxonox::crossexecute 0 $id $args }");
103                this->interpreter_->eval("proc running      {}        { return 1 }");
104                this->interpreter_->eval("set id 0");
105                this->interpreter_->eval("rename exit ::tcl::exit; proc exit {} { execute exit }");
106            }
107            catch (Tcl::tcl_error const &e)
108            {   orxout(internal_error, context::tcl) << "Tcl error while creating Tcl-interpreter: " << e.what() << endl;   }
109        }
110    }
111
112    /**
113        @brief Creates and initializes a new Tcl-interpreter and calls the Orxonox-specific
114        init.tcl script that defines some special functions which are required by Orxonox.
115    */
116    Tcl::interpreter* TclBind::createTclInterpreter()
117    {
118        Tcl::interpreter* interpreter = new Tcl::interpreter();
119        const std::string& libpath = TclBind::getTclLibraryPath();
120
121        try
122        {
123            if (!libpath.empty())
124                interpreter->eval("set tcl_library \"" + libpath + '"');
125
126            Tcl_Init(interpreter->get());
127
128            interpreter->eval("source \"" + TclBind::getInstance().tclDataPath_ + "/init.tcl\"");
129        }
130        catch (Tcl::tcl_error const &e)
131        {
132            orxout(internal_error, context::tcl) << "Tcl error while creating Tcl-interpreter: " << e.what() << endl;
133            orxout(user_error, context::tcl) << "Tcl isn't properly initialized. Orxonox might possibly not work like that." << endl;
134        }
135
136        return interpreter;
137    }
138
139    /**
140        @brief Returns the path to the Tcl-library (not the Orxonox-specific Tcl-files).
141    */
142    std::string TclBind::getTclLibraryPath()
143    {
144#ifdef DEPENDENCY_PACKAGE_ENABLE
145        if (PathConfig::buildDirectoryRun())
146            return (std::string(specialConfig::dependencyLibraryDirectory) + "/tcl");
147        else
148            return (PathConfig::getRootPathString() + specialConfig::defaultLibraryPath + "/tcl");
149#else
150        return "";
151#endif
152    }
153
154    /**
155        @brief Callback: Used to send an Orxonox-command from Tcl to the CommandExecutor and to send its result back to Tcl.
156    */
157    std::string TclBind::tcl_query(Tcl::object const &args)
158    {
159        orxout(verbose, context::commands) << "Tcl_query: " << args.get() << endl;
160        return TclBind::tcl_helper(args, true);
161    }
162
163    /**
164        @brief Callback: Used to send an Orxonox-command from Tcl to the CommandExecutor.
165    */
166    void TclBind::tcl_execute(Tcl::object const &args)
167    {
168        orxout(verbose, context::commands) << "Tcl_execute: " << args.get() << endl;
169        TclBind::tcl_helper(args, false);
170    }
171
172    /**
173        @brief Helper function, used by tcl_query() and tcl_execute().
174    */
175    std::string TclBind::tcl_helper(Tcl::object const &args, bool bQuery)
176    {
177        const std::string& command = stripEnclosingBraces(args.get());
178
179        int error;
180        std::string result;
181
182        CommandEvaluation evaluation = CommandExecutor::evaluate(command);
183
184        if (bQuery)
185            result = evaluation.query(&error).get<std::string>();
186        else
187            error = evaluation.execute();
188
189        if (error)
190        {
191            orxout(user_error) << "Can't execute command \"" << command << "\", " + CommandExecutor::getErrorDescription(error) + ". (TclBind)" << endl;
192            if (error == CommandExecutor::Inexistent)
193                orxout(user_info) << "Did you mean \"" << evaluation.getCommandSuggestion() << "\"?" << endl;
194        }
195
196        return result;
197    }
198
199    /**
200        @brief Console command, executes Tcl code. Can be used to bind Tcl-commands to a key, because native
201        Tcl-commands can not be evaluated and are thus not supported by the key-binder.
202    */
203    std::string TclBind::tcl(const std::string& tclcode)
204    {
205        if (TclBind::getInstance().interpreter_)
206        {
207            try
208            {
209                return TclBind::getInstance().interpreter_->eval("uplevel #0 " + tclcode);
210            }
211            catch (Tcl::tcl_error const &e)
212            {   orxout(user_error, context::tcl) << "Tcl error: " << e.what() << endl;   }
213        }
214
215        return "";
216    }
217
218    /**
219        @brief Console command and implementation of the Tcl-feature "bgerror" which is called if an error
220        occurred in the background of a Tcl-script.
221    */
222    void TclBind::bgerror(const std::string& error)
223    {
224        orxout(user_error, context::tcl) << "Tcl background error: " << stripEnclosingBraces(error) << endl;
225    }
226
227    /**
228        @brief Executes Tcl-code and returns the return-value.
229        @param tclcode A string that contains Tcl-code
230        @param error A pointer to an integer (or NULL) that is used to write an error-code (see @ref CommandExecutorErrorCodes "CommandExecutor error codes")
231        @return Returns the return-value of the executed code (or an empty string if there's no return-value)
232    */
233    std::string TclBind::eval(const std::string& tclcode, int* error)
234    {
235        if (error)
236            *error = CommandExecutor::Success;
237
238        try
239        {
240            // execute the code
241            return TclBind::getInstance().interpreter_->eval(tclcode);
242        }
243        catch (Tcl::tcl_error const &e)
244        {   orxout(user_error, context::tcl) << "Tcl error: " << e.what() << endl;   }
245
246        if (error)
247            *error = CommandExecutor::Error;
248        return "";
249    }
250}
Note: See TracBrowser for help on using the repository browser.