1 //
2 //  SuperTuxKart - a fun racing game with go-kart
3 //  Copyright (C) 2014-2015  SuperTuxKart Team
4 //
5 //  This program is free software; you can redistribute it and/or
6 //  modify it under the terms of the GNU General Public License
7 //  as published by the Free Software Foundation; either version 3
8 //  of the License, or (at your option) any later version.
9 //
10 //  This program is distributed in the hope that it will be useful,
11 //  but WITHOUT ANY WARRANTY; without even the implied warranty of
12 //  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
13 //  GNU General Public License for more details.
14 //
15 //  You should have received a copy of the GNU General Public License
16 //  along with this program; if not, write to the Free Software
17 //  Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
18 
19 extern "C"
20 {
21     #include <mcpp_lib.h>
22 }
23 #include <assert.h>
24 #include <angelscript.h>
25 #include "io/file_manager.hpp"
26 #include "karts/kart.hpp"
27 #include "modes/world.hpp"
28 #include "scriptengine/aswrappedcall.hpp"
29 #include "scriptengine/script_audio.hpp"
30 #include "scriptengine/script_challenges.hpp"
31 #include "scriptengine/script_kart.hpp"
32 #include "scriptengine/script_engine.hpp"
33 #include "scriptengine/script_gui.hpp"
34 #include "scriptengine/script_physics.hpp"
35 #include "scriptengine/script_track.hpp"
36 #include "scriptengine/script_utils.hpp"
37 #include "scriptengine/scriptstdstring.hpp"
38 #include "scriptengine/scriptvec3.hpp"
39 #include "scriptengine/scriptarray.hpp"
40 #include <string.h>
41 #include "states_screens/dialogs/tutorial_message_dialog.hpp"
42 #include "tracks/track_object_manager.hpp"
43 #include "tracks/track.hpp"
44 #include "utils/file_utils.hpp"
45 #include "utils/string_utils.hpp"
46 #include "utils/profiler.hpp"
47 
48 
49 using namespace Scripting;
50 
51 namespace Scripting
52 {
53     const char* MODULE_ID_MAIN_SCRIPT_FILE = "main";
54 
AngelScript_ErrorCallback(const asSMessageInfo * msg,void * param)55     void AngelScript_ErrorCallback (const asSMessageInfo *msg, void *param)
56     {
57         const char *type = "ERR ";
58         if (msg->type == asMSGTYPE_WARNING)
59             type = "WARN";
60         else if (msg->type == asMSGTYPE_INFORMATION)
61             type = "INFO";
62 
63         Log::warn("Scripting", "%s (%d, %d) : %s : %s\n", msg->section, msg->row, msg->col, type, msg->message);
64     }
65 
66 
67     //Constructor, creates a new Scripting Engine using AngelScript
ScriptEngine()68     ScriptEngine::ScriptEngine()
69     {
70         // Create the script engine
71         m_engine = asCreateScriptEngine(ANGELSCRIPT_VERSION);
72         if (m_engine == NULL)
73         {
74             Log::fatal("Scripting", "Failed to create script engine.");
75         }
76 
77         // The script compiler will write any compiler messages to the callback.
78         m_engine->SetMessageCallback(asFUNCTION(AngelScript_ErrorCallback), 0, asCALL_CDECL);
79 
80         // Configure the script engine with all the functions,
81         // and variables that the script should be able to use.
82         configureEngine(m_engine);
83     }
84 
~ScriptEngine()85     ScriptEngine::~ScriptEngine()
86     {
87         // Release the engine
88         m_pending_timeouts.clearAndDeleteAll();
89         m_engine->DiscardModule(MODULE_ID_MAIN_SCRIPT_FILE);
90         m_engine->Release();
91     }
92 
93 
94 
95     /** Get Script By it's file name
96     *  \param string scriptname = name of script to get
97     *  \return      The corresponding script
98     */
getScript(std::string script_path)99     std::string getScript(std::string script_path)
100     {
101         std::string script_file = FileUtils::getPortableReadingPath(script_path);
102         if (!file_manager->fileExists(script_file))
103         {
104 #ifndef SERVER_ONLY
105             Log::debug("Scripting", "File does not exist : %s", script_path.c_str());
106 #endif
107             return "";
108         }
109 
110         // libmcpp ignores the first argument (like real main which is the exe)
111         std::string cmd1 = "mcpp";
112         std::string int_version =
113             StringUtils::toString(StringUtils::versionToInt(STK_VERSION));
114         // Preprocessing (atm add stk version)
115         std::string cmd2  = "-DSTK_VERSION=";
116         cmd2 += int_version;
117         // -P Don't output #line lines.
118         std::string cmd3 = "-P";
119         // -j Don't output the source line in diagnostics.
120         std::string cmd4 = "-j";
121         // -e <encoding>   Change the default multi-byte character encoding to one of:
122         //    euc_jp, gb2312, ksc5601, big5, sjis, iso2022_jp, utf8.
123         std::string cmd5 = "-e";
124         std::string cmd6 = "utf8";
125         std::string cmd7 = script_file;
126         std::vector<char*> all_cmds;
127         all_cmds.push_back(&cmd1[0]);
128         all_cmds.push_back(&cmd2[0]);
129         all_cmds.push_back(&cmd3[0]);
130         all_cmds.push_back(&cmd4[0]);
131         all_cmds.push_back(&cmd5[0]);
132         all_cmds.push_back(&cmd6[0]);
133         all_cmds.push_back(&cmd7[0]);
134         mcpp_use_mem_buffers(1);
135         mcpp_lib_main(all_cmds.size(), all_cmds.data());
136 
137         char* err = mcpp_get_mem_buffer((OUTDEST)1/*error buffer*/);
138         bool has_error = false;
139         if (err)
140         {
141             std::string total = err;
142             auto errs = StringUtils::split(total, '\n');
143             for (auto& e : errs)
144             {
145                 if (e.find("warning: Converted [CR+LF] to [LF]") !=
146                     std::string::npos)
147                     continue;
148 
149                 if (e.find("fatal:") != std::string::npos ||
150                     e.find("error:") != std::string::npos)
151                 {
152                     has_error = true;
153                     Log::error("Scripting preprocessing", "%s", e.c_str());
154                 }
155                 else
156                 {
157                     Log::warn("Scripting preprocessing", "%s", e.c_str());
158                 }
159             }
160         }
161 
162         std::string result;
163         if (!has_error)
164         {
165             char* buf = mcpp_get_mem_buffer((OUTDEST)0/*output buffer*/);
166             if (buf)
167                 result = buf;
168         }
169 
170         // Calling this again causes the memory buffers to be freed.
171         mcpp_use_mem_buffers(1);
172         return result;
173     }
174 
175     //-----------------------------------------------------------------------------
176 
evalScript(std::string script_fragment)177     void ScriptEngine::evalScript(std::string script_fragment)
178     {
179         script_fragment = "void evalScript_main() { \n" + script_fragment + "\n}";
180 
181         asIScriptModule* mod = m_engine->GetModule(MODULE_ID_MAIN_SCRIPT_FILE, asGM_ONLY_IF_EXISTS);
182 
183         asIScriptFunction* func;
184         int r = mod->CompileFunction("eval", script_fragment.c_str(), 0, 0, &func);
185         if (r < 0)
186         {
187             Log::error("Scripting", "evalScript: CompileFunction() failed");
188             return;
189         }
190 
191         asIScriptContext *ctx = m_engine->CreateContext();
192         if (ctx == NULL)
193         {
194             Log::error("Scripting", "evalScript: Failed to create the context.");
195             //m_engine->Release();
196             return;
197         }
198 
199         r = ctx->Prepare(func);
200         if (r < 0)
201         {
202             Log::error("Scripting", "evalScript: Failed to prepare the context.");
203             ctx->Release();
204             return;
205         }
206 
207         // Execute the function
208         r = ctx->Execute();
209         if (r != asEXECUTION_FINISHED)
210         {
211             // The execution didn't finish as we had planned. Determine why.
212             if (r == asEXECUTION_ABORTED)
213             {
214                 Log::error("Scripting", "The script was aborted before it could finish. Probably it timed out.");
215             }
216             else if (r == asEXECUTION_EXCEPTION)
217             {
218                 Log::error("Scripting", "The script ended with an exception.");
219             }
220             else
221             {
222                 Log::error("Scripting", "The script ended for some unforeseen reason (%i)", r);
223             }
224         }
225 
226         ctx->Release();
227         func->Release();
228     }
229 
230     //-----------------------------------------------------------------------------
231 
runDelegate(asIScriptFunction * delegate)232     void ScriptEngine::runDelegate(asIScriptFunction* delegate)
233     {
234         asIScriptContext *ctx = m_engine->CreateContext();
235         if (ctx == NULL)
236         {
237             Log::error("Scripting", "runMethod: Failed to create the context.");
238             //m_engine->Release();
239             return;
240         }
241 
242         int r = ctx->Prepare(delegate);
243         if (r < 0)
244         {
245             Log::error("Scripting", "runMethod: Failed to prepare the context.");
246             ctx->Release();
247             return;
248         }
249 
250         // Execute the function
251         r = ctx->Execute();
252         if (r != asEXECUTION_FINISHED)
253         {
254             // The execution didn't finish as we had planned. Determine why.
255             if (r == asEXECUTION_ABORTED)
256             {
257                 Log::error("Scripting", "The script was aborted before it could finish. Probably it timed out.");
258             }
259             else if (r == asEXECUTION_EXCEPTION)
260             {
261                 Log::error("Scripting", "The script ended with an exception : (line %i) %s",
262                     ctx->GetExceptionLineNumber(),
263                     ctx->GetExceptionString());
264             }
265             else
266             {
267                 Log::error("Scripting", "The script ended for some unforeseen reason (%i)", r);
268             }
269         }
270 
271         ctx->Release();
272     }
273 
274     //-----------------------------------------------------------------------------
275 
276     /*
277     void ScriptEngine::runMethod(asIScriptObject* obj, std::string methodName)
278     {
279         asITypeInfo* type = obj->GetObjectType();
280         asIScriptFunction* method = type->GetMethodByName(methodName.c_str());
281         if (method == NULL)
282             Log::error("Scripting", ("runMethod: object does not implement method " + methodName).c_str());
283 
284 
285         asIScriptContext *ctx = m_engine->CreateContext();
286         if (ctx == NULL)
287         {
288             Log::error("Scripting", "runMethod: Failed to create the context.");
289             //m_engine->Release();
290             return;
291         }
292 
293         int r = ctx->Prepare(method);
294         if (r < 0)
295         {
296             Log::error("Scripting", "runMethod: Failed to prepare the context.");
297             ctx->Release();
298             return;
299         }
300 
301         // Execute the function
302         r = ctx->Execute();
303         if (r != asEXECUTION_FINISHED)
304         {
305             // The execution didn't finish as we had planned. Determine why.
306             if (r == asEXECUTION_ABORTED)
307             {
308                 Log::error("Scripting", "The script was aborted before it could finish. Probably it timed out.");
309             }
310             else if (r == asEXECUTION_EXCEPTION)
311             {
312                 Log::error("Scripting", "The script ended with an exception.");
313             }
314             else
315             {
316                 Log::error("Scripting", "The script ended for some unforeseen reason (%i)", r);
317             }
318         }
319 
320         ctx->Release();
321     }
322     */
323     //-----------------------------------------------------------------------------
324 
325     /** runs the specified script
326     *  \param string scriptName = name of script to run
327     */
runFunction(bool warn_if_not_found,std::string function_name)328     void ScriptEngine::runFunction(bool warn_if_not_found, std::string function_name)
329     {
330         std::function<void(asIScriptContext*)> callback;
331         std::function<void(asIScriptContext*)> get_return_value;
332         runFunction(warn_if_not_found, function_name, callback, get_return_value);
333     }
334 
335     //-----------------------------------------------------------------------------
336 
runFunction(bool warn_if_not_found,std::string function_name,std::function<void (asIScriptContext *)> callback)337     void ScriptEngine::runFunction(bool warn_if_not_found, std::string function_name,
338         std::function<void(asIScriptContext*)> callback)
339     {
340         std::function<void(asIScriptContext*)> get_return_value;
341         runFunction(warn_if_not_found, function_name, callback, get_return_value);
342     }
343 
344     //-----------------------------------------------------------------------------
345 
346     /** runs the specified script
347     *  \param string scriptName = name of script to run
348     */
runFunction(bool warn_if_not_found,std::string function_name,std::function<void (asIScriptContext *)> callback,std::function<void (asIScriptContext *)> get_return_value)349     void ScriptEngine::runFunction(bool warn_if_not_found, std::string function_name,
350         std::function<void(asIScriptContext*)> callback,
351         std::function<void(asIScriptContext*)> get_return_value)
352     {
353         int r; //int for error checking
354 
355         asIScriptFunction *func;
356 
357         // TODO: allow splitting in multiple files
358         std::string script_filename = "scripting.as";
359         auto cached_function = m_functions_cache.find(function_name);
360         if (cached_function == m_functions_cache.end())
361         {
362             // Find the function for the function we want to execute.
363             //      This is how you call a normal function with arguments
364             //      asIScriptFunction *func = engine->GetModule(0)->GetFunctionByDecl("void func(arg1Type, arg2Type)");
365             asIScriptModule* module = m_engine->GetModule(MODULE_ID_MAIN_SCRIPT_FILE);
366 
367             if (module == NULL)
368             {
369 #ifndef SERVER_ONLY
370                 if (warn_if_not_found)
371                     Log::warn("Scripting", "Scripting function was not found : %s (module not found)", function_name.c_str());
372                 else
373                     Log::debug("Scripting", "Scripting function was not found : %s (module not found)", function_name.c_str());
374 #endif
375                 m_functions_cache[function_name] = NULL; // remember that this function is unavailable
376                 return;
377             }
378 
379             func = module->GetFunctionByDecl(function_name.c_str());
380 
381             if (func == NULL)
382             {
383 #ifndef SERVER_ONLY
384                 if (warn_if_not_found)
385                     Log::warn("Scripting", "Scripting function was not found : %s", function_name.c_str());
386                 else
387                     Log::debug("Scripting", "Scripting function was not found : %s", function_name.c_str());
388 #endif
389                 m_functions_cache[function_name] = NULL; // remember that this function is unavailable
390                 return;
391             }
392 
393             m_functions_cache[function_name] = func;
394             func->AddRef();
395         }
396         else
397         {
398             // Script present in cache
399             func = cached_function->second;
400         }
401 
402         if (func == NULL)
403         {
404             if (warn_if_not_found)
405                 Log::warn("Scripting", "Scripting function was not found : %s", function_name.c_str());
406             return; // function unavailable
407         }
408 
409         // Create a context that will execute the script.
410         asIScriptContext *ctx = m_engine->CreateContext();
411         if (ctx == NULL)
412         {
413             Log::error("Scripting", "Failed to create the context.");
414             //m_engine->Release();
415             return;
416         }
417 
418         // Prepare the script context with the function we wish to execute. Prepare()
419         // must be called on the context before each new script function that will be
420         // executed. Note, that if because we intend to execute the same function
421         // several times, we will store the function returned by
422         // GetFunctionByDecl(), so that this relatively slow call can be skipped.
423         r = ctx->Prepare(func);
424         if (r < 0)
425         {
426             Log::error("Scripting", "Failed to prepare the context.");
427             ctx->Release();
428             //m_engine->Release();
429             return;
430         }
431 
432         // Here, we can pass parameters to the script functions.
433         //ctx->setArgType(index, value);
434         //for example : ctx->SetArgFloat(0, 3.14159265359f);
435 
436         if (callback)
437             callback(ctx);
438 
439         // Execute the function
440         r = ctx->Execute();
441         if (r != asEXECUTION_FINISHED)
442         {
443             // The execution didn't finish as we had planned. Determine why.
444             if (r == asEXECUTION_ABORTED)
445             {
446                 Log::error("Scripting", "The script was aborted before it could finish. Probably it timed out.");
447             }
448             else if (r == asEXECUTION_EXCEPTION)
449             {
450                 Log::error("Scripting", "The script ended with an exception.");
451 
452                 // Write some information about the script exception
453                 //asIScriptFunction *func = ctx->GetExceptionFunction();
454                 //std::cout << "func: " << func->GetDeclaration() << std::endl;
455                 //std::cout << "modl: " << func->GetModuleName() << std::endl;
456                 //std::cout << "sect: " << func->GetScriptSectionName() << std::endl;
457                 //std::cout << "line: " << ctx->GetExceptionLineNumber() << std::endl;
458                 //std::cout << "desc: " << ctx->GetExceptionString() << std::endl;
459             }
460             else
461             {
462                 Log::error("Scripting", "The script ended for some unforeseen reason (%i)", r);
463             }
464         }
465         else
466         {
467             // Retrieve the return value from the context here (for scripts that return values)
468             // <type> returnValue = ctx->getReturnType(); for example
469             //float returnValue = ctx->GetReturnFloat();
470 
471             if (get_return_value)
472                 get_return_value(ctx);
473         }
474 
475         // We must release the contexts when no longer using them
476         ctx->Release();
477     }
478 
479     //-----------------------------------------------------------------------------
480 
cleanupCache()481     void ScriptEngine::cleanupCache()
482     {
483         for (auto curr : m_functions_cache)
484         {
485             if (curr.second != NULL)
486                 curr.second->Release();
487         }
488         m_functions_cache.clear();
489         m_engine->DiscardModule(MODULE_ID_MAIN_SCRIPT_FILE);
490     }
491 
492     //-----------------------------------------------------------------------------
493     /** Configures the script engine by binding functions, enums
494     *  \param asIScriptEngine engine = engine to configure
495     */
configureEngine(asIScriptEngine * engine)496     void ScriptEngine::configureEngine(asIScriptEngine *engine)
497     {
498         // Register the script string type
499         RegisterStdString(engine); //register std::string
500         RegisterVec3(engine);      //register Vec3
501         RegisterScriptArray(engine, true);
502 
503         Scripting::Track::registerScriptFunctions(m_engine);
504         Scripting::Challenges::registerScriptFunctions(m_engine);
505         Scripting::Kart::registerScriptFunctions(m_engine);
506         Scripting::Kart::registerScriptEnums(m_engine);
507         Scripting::Physics::registerScriptFunctions(m_engine);
508         Scripting::Utils::registerScriptFunctions(m_engine);
509         Scripting::GUI::registerScriptEnums(m_engine);
510         Scripting::GUI::registerScriptFunctions(m_engine);
511         Scripting::Audio::registerScriptFunctions(m_engine);
512 
513         // It is possible to register the functions, properties, and types in
514         // configuration groups as well. When compiling the scripts it can then
515         // be defined which configuration groups should be available for that
516         // script. If necessary a configuration group can also be removed from
517         // the engine, so that the engine configuration could be changed
518         // without having to recompile all the scripts.
519     }
520 
521     //-----------------------------------------------------------------------------
522 
loadScript(std::string script_path,bool clear_previous)523     bool ScriptEngine::loadScript(std::string script_path, bool clear_previous)
524     {
525         int r;
526 
527         std::string script = getScript(script_path);
528         if (script.size() == 0)
529         {
530             // No such file
531             return false;
532         }
533 
534         // Add the script sections that will be compiled into executable code.
535         // If we want to combine more than one file into the same script, then
536         // we can call AddScriptSection() several times for the same module and
537         // the script engine will treat them all as if they were one. The script
538         // section name, will allow us to localize any errors in the script code.
539         asIScriptModule *mod = m_engine->GetModule(MODULE_ID_MAIN_SCRIPT_FILE,
540             clear_previous ? asGM_ALWAYS_CREATE : asGM_CREATE_IF_NOT_EXISTS);
541         r = mod->AddScriptSection("script", &script[0], script.size());
542         if (r < 0)
543         {
544             Log::error("Scripting", "AddScriptSection() failed");
545             return false;
546         }
547 
548         return true;
549     }
550 
551     //-----------------------------------------------------------------------------
552 
compileLoadedScripts()553     bool ScriptEngine::compileLoadedScripts()
554     {
555         int r;
556         asIScriptModule *mod = m_engine->GetModule(MODULE_ID_MAIN_SCRIPT_FILE, asGM_CREATE_IF_NOT_EXISTS);
557 
558         // Compile the script. If there are any compiler messages they will
559         // be written to the message stream that we set right after creating the
560         // script engine. If there are no errors, and no warnings, nothing will
561         // be written to the stream.
562         r = mod->Build();
563         if (r < 0)
564         {
565             Log::error("Scripting", "Build() failed");
566             return false;
567         }
568 
569         // The engine doesn't keep a copy of the script sections after Build() has
570         // returned. So if the script needs to be recompiled, then all the script
571         // sections must be added again.
572 
573         // If we want to have several scripts executing at different times but
574         // that have no direct relation with each other, then we can compile them
575         // into separate script modules. Each module uses their own namespace and
576         // scope, so function names, and global variables will not conflict with
577         // each other.
578 
579         return true;
580     }
581 
582     //-----------------------------------------------------------------------------
583 
PendingTimeout(double time,asIScriptFunction * callback_delegate)584     PendingTimeout::PendingTimeout(double time, asIScriptFunction* callback_delegate)
585     {
586         m_time = time;
587         m_callback_delegate = callback_delegate;
588 
589         #if ANGELSCRIPT_VERSION < 23300
590         if (strstr(asGetLibraryOptions(), "AS_MAX_PORTABILITY"))
591         {
592             callback_delegate->AddRef();
593         }
594         #endif
595     }
596 
597     //-----------------------------------------------------------------------------
598 
~PendingTimeout()599     PendingTimeout::~PendingTimeout()
600     {
601         if (m_callback_delegate != NULL)
602         {
603             m_callback_delegate->Release();
604         }
605     }
606 
607     //-----------------------------------------------------------------------------
608 
addPendingTimeout(double time,const std::string & callback_name)609     void ScriptEngine::addPendingTimeout(double time, const std::string& callback_name)
610     {
611         m_pending_timeouts.push_back(new PendingTimeout(time, callback_name));
612     }
613 
614     //-----------------------------------------------------------------------------
615 
addPendingTimeout(double time,asIScriptFunction * delegate)616     void ScriptEngine::addPendingTimeout(double time, asIScriptFunction* delegate)
617     {
618         m_pending_timeouts.push_back(new PendingTimeout(time, delegate));
619     }
620 
621     //-----------------------------------------------------------------------------
622 
update(float dt)623     void ScriptEngine::update(float dt)
624     {
625         for (int i = m_pending_timeouts.size() - 1; i >= 0; i--)
626         {
627             PendingTimeout& curr = m_pending_timeouts[i];
628             curr.m_time -= dt;
629             if (curr.m_time <= 0.0)
630             {
631                 if (curr.m_callback_delegate != NULL)
632                 {
633                     runDelegate(curr.m_callback_delegate);
634                 }
635                 else
636                 {
637                     runFunction(true, "void " + curr.m_callback_name + "()");
638                 }
639 
640                 m_pending_timeouts.erase(i);
641             }
642         }
643     }
644 }
645