1 //
2 // Copyright (c) 2008-2017 the Urho3D project.
3 //
4 // Permission is hereby granted, free of charge, to any person obtaining a copy
5 // of this software and associated documentation files (the "Software"), to deal
6 // in the Software without restriction, including without limitation the rights
7 // to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
8 // copies of the Software, and to permit persons to whom the Software is
9 // furnished to do so, subject to the following conditions:
10 //
11 // The above copyright notice and this permission notice shall be included in
12 // all copies or substantial portions of the Software.
13 //
14 // THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
15 // IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
16 // FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
17 // AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
18 // LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
19 // OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN
20 // THE SOFTWARE.
21 //
22 
23 #include "../Precompiled.h"
24 
25 #include "../Core/CoreEvents.h"
26 #include "../Core/ProcessUtils.h"
27 #include "../Core/Profiler.h"
28 #include "../Engine/EngineEvents.h"
29 #include "../IO/File.h"
30 #include "../IO/FileSystem.h"
31 #include "../IO/Log.h"
32 #include "../LuaScript/LuaFile.h"
33 #include "../LuaScript/LuaFunction.h"
34 #include "../LuaScript/LuaScript.h"
35 #include "../LuaScript/LuaScriptEventInvoker.h"
36 #include "../LuaScript/LuaScriptInstance.h"
37 #include "../Resource/ResourceCache.h"
38 #include "../Scene/Scene.h"
39 
40 extern "C"
41 {
42 #include <lualib.h>
43 }
44 
45 #include <toluapp/tolua++.h>
46 #include "../LuaScript/ToluaUtils.h"
47 
48 #include "../DebugNew.h"
49 
50 extern int tolua_AudioLuaAPI_open(lua_State*);
51 extern int tolua_CoreLuaAPI_open(lua_State*);
52 extern int tolua_EngineLuaAPI_open(lua_State*);
53 extern int tolua_GraphicsLuaAPI_open(lua_State*);
54 extern int tolua_InputLuaAPI_open(lua_State*);
55 extern int tolua_IOLuaAPI_open(lua_State*);
56 extern int tolua_MathLuaAPI_open(lua_State*);
57 #ifdef URHO3D_NAVIGATION
58 extern int tolua_NavigationLuaAPI_open(lua_State*);
59 #endif
60 #ifdef URHO3D_NETWORK
61 extern int tolua_NetworkLuaAPI_open(lua_State*);
62 #endif
63 #ifdef URHO3D_DATABASE
64 extern int tolua_DatabaseLuaAPI_open(lua_State*);
65 #endif
66 #ifdef URHO3D_IK
67 extern int tolua_IKLuaAPI_open(lua_State*);
68 #endif
69 #ifdef URHO3D_PHYSICS
70 extern int tolua_PhysicsLuaAPI_open(lua_State*);
71 #endif
72 extern int tolua_ResourceLuaAPI_open(lua_State*);
73 extern int tolua_SceneLuaAPI_open(lua_State*);
74 extern int tolua_UILuaAPI_open(lua_State*);
75 #ifdef URHO3D_URHO2D
76 extern int tolua_Urho2DLuaAPI_open(lua_State*);
77 #endif
78 extern int tolua_LuaScriptLuaAPI_open(lua_State*);
79 
80 namespace Urho3D
81 {
82 
LuaScript(Context * context)83 LuaScript::LuaScript(Context* context) :
84     Object(context),
85     luaState_(0),
86     executeConsoleCommands_(false)
87 {
88     RegisterLuaScriptLibrary(context_);
89 
90     luaState_ = luaL_newstate();
91     if (!luaState_)
92     {
93         URHO3D_LOGERROR("Could not create Lua state");
94         return;
95     }
96 
97     lua_atpanic(luaState_, &LuaScript::AtPanic);
98 
99     luaL_openlibs(luaState_);
100     RegisterLoader();
101     ReplacePrint();
102 
103     tolua_MathLuaAPI_open(luaState_);
104     tolua_CoreLuaAPI_open(luaState_);
105     tolua_IOLuaAPI_open(luaState_);
106     tolua_ResourceLuaAPI_open(luaState_);
107     tolua_SceneLuaAPI_open(luaState_);
108     tolua_AudioLuaAPI_open(luaState_);
109     tolua_EngineLuaAPI_open(luaState_);
110     tolua_GraphicsLuaAPI_open(luaState_);
111     tolua_InputLuaAPI_open(luaState_);
112 #ifdef URHO3D_NAVIGATION
113     tolua_NavigationLuaAPI_open(luaState_);
114 #endif
115 #ifdef URHO3D_NETWORK
116     tolua_NetworkLuaAPI_open(luaState_);
117 #endif
118 #ifdef URHO3D_DATABASE
119     tolua_DatabaseLuaAPI_open(luaState_);
120 #endif
121 #ifdef URHO3D_IK
122     tolua_IKLuaAPI_open(luaState_);
123 #endif
124 #ifdef URHO3D_PHYSICS
125     tolua_PhysicsLuaAPI_open(luaState_);
126 #endif
127     tolua_UILuaAPI_open(luaState_);
128 #ifdef URHO3D_URHO2D
129     tolua_Urho2DLuaAPI_open(luaState_);
130 #endif
131     tolua_LuaScriptLuaAPI_open(luaState_);
132 
133     SetContext(luaState_, context_);
134 
135     eventInvoker_ = new LuaScriptEventInvoker(context_);
136     coroutineUpdate_ = GetFunction("coroutine.update");
137 
138     // Subscribe to post update
139     SubscribeToEvent(E_POSTUPDATE, URHO3D_HANDLER(LuaScript, HandlePostUpdate));
140 
141     // Subscribe to console commands
142     SetExecuteConsoleCommands(true);
143 }
144 
~LuaScript()145 LuaScript::~LuaScript()
146 {
147     functionPointerToFunctionMap_.Clear();
148     functionNameToFunctionMap_.Clear();
149 
150     lua_State* luaState = luaState_;
151     luaState_ = 0;
152     coroutineUpdate_ = 0;
153 
154     if (luaState)
155         lua_close(luaState);
156 }
157 
AddEventHandler(const String & eventName,int index)158 void LuaScript::AddEventHandler(const String& eventName, int index)
159 {
160     LuaFunction* function = GetFunction(index);
161     if (function)
162         eventInvoker_->AddEventHandler(0, eventName, function);
163 }
164 
AddEventHandler(const String & eventName,const String & functionName)165 void LuaScript::AddEventHandler(const String& eventName, const String& functionName)
166 {
167     LuaFunction* function = GetFunction(functionName);
168     if (function)
169         eventInvoker_->AddEventHandler(0, eventName, function);
170 }
171 
AddEventHandler(Object * sender,const String & eventName,int index)172 void LuaScript::AddEventHandler(Object* sender, const String& eventName, int index)
173 {
174     if (!sender)
175         return;
176 
177     LuaFunction* function = GetFunction(index);
178     if (function)
179         eventInvoker_->AddEventHandler(sender, eventName, function);
180 }
181 
AddEventHandler(Object * sender,const String & eventName,const String & functionName)182 void LuaScript::AddEventHandler(Object* sender, const String& eventName, const String& functionName)
183 {
184     if (!sender)
185         return;
186 
187     LuaFunction* function = GetFunction(functionName);
188     if (function)
189         eventInvoker_->AddEventHandler(sender, eventName, function);
190 }
191 
RemoveEventHandler(const String & eventName)192 void LuaScript::RemoveEventHandler(const String& eventName)
193 {
194     eventInvoker_->UnsubscribeFromEvent(eventName);
195 }
196 
RemoveEventHandler(Object * sender,const String & eventName)197 void LuaScript::RemoveEventHandler(Object* sender, const String& eventName)
198 {
199     if (!sender)
200         return;
201 
202     eventInvoker_->UnsubscribeFromEvent(sender, eventName);
203 }
204 
RemoveEventHandlers(Object * sender)205 void LuaScript::RemoveEventHandlers(Object* sender)
206 {
207     if (!sender)
208         return;
209 
210     eventInvoker_->UnsubscribeFromEvents(sender);
211 }
212 
RemoveAllEventHandlers()213 void LuaScript::RemoveAllEventHandlers()
214 {
215     eventInvoker_->UnsubscribeFromAllEvents();
216 }
217 
RemoveEventHandlersExcept(const Vector<String> & exceptionNames)218 void LuaScript::RemoveEventHandlersExcept(const Vector<String>& exceptionNames)
219 {
220     PODVector<StringHash> exceptionTypes(exceptionNames.Size());
221     for (unsigned i = 0; i < exceptionTypes.Size(); ++i)
222         exceptionTypes[i] = StringHash(exceptionNames[i]);
223 
224     eventInvoker_->UnsubscribeFromAllEventsExcept(exceptionTypes, true);
225 }
226 
HasEventHandler(const String & eventName) const227 bool LuaScript::HasEventHandler(const String& eventName) const
228 {
229     return eventInvoker_->HasSubscribedToEvent(eventName);
230 }
231 
HasEventHandler(Object * sender,const String & eventName) const232 bool LuaScript::HasEventHandler(Object* sender, const String& eventName) const
233 {
234     return eventInvoker_->HasSubscribedToEvent(sender, eventName);
235 }
236 
ExecuteFile(const String & fileName)237 bool LuaScript::ExecuteFile(const String& fileName)
238 {
239     URHO3D_PROFILE(ExecuteFile);
240 
241 #ifdef URHO3D_LUA_RAW_SCRIPT_LOADER
242     if (ExecuteRawFile(fileName))
243         return true;
244 #endif
245 
246     ResourceCache* cache = GetSubsystem<ResourceCache>();
247     LuaFile* luaFile = cache->GetResource<LuaFile>(fileName);
248     return luaFile && luaFile->LoadAndExecute(luaState_);
249 }
250 
ExecuteString(const String & string)251 bool LuaScript::ExecuteString(const String& string)
252 {
253     URHO3D_PROFILE(ExecuteString);
254 
255     if (luaL_dostring(luaState_, string.CString()))
256     {
257         const char* message = lua_tostring(luaState_, -1);
258         URHO3D_LOGERRORF("Execute Lua string failed: %s", message);
259         lua_pop(luaState_, 1);
260         return false;
261     }
262 
263     return true;
264 }
265 
LoadRawFile(const String & fileName)266 bool LuaScript::LoadRawFile(const String& fileName)
267 {
268     URHO3D_PROFILE(LoadRawFile);
269 
270     URHO3D_LOGINFO("Finding Lua file on file system: " + fileName);
271 
272     ResourceCache* cache = GetSubsystem<ResourceCache>();
273     String filePath = cache->GetResourceFileName(fileName);
274 
275     if (filePath.Empty())
276     {
277         URHO3D_LOGINFO("Lua file not found: " + fileName);
278         return false;
279     }
280 
281     filePath = GetNativePath(filePath);
282 
283     URHO3D_LOGINFO("Loading Lua file from file system: " + filePath);
284 
285     if (luaL_loadfile(luaState_, filePath.CString()))
286     {
287         const char* message = lua_tostring(luaState_, -1);
288         URHO3D_LOGERRORF("Load Lua file failed: %s", message);
289         lua_pop(luaState_, 1);
290         return false;
291     }
292 
293     URHO3D_LOGINFO("Lua file loaded: " + filePath);
294 
295     return true;
296 }
297 
ExecuteRawFile(const String & fileName)298 bool LuaScript::ExecuteRawFile(const String& fileName)
299 {
300     URHO3D_PROFILE(ExecuteRawFile);
301 
302     if (!LoadRawFile(fileName))
303         return false;
304 
305     if (lua_pcall(luaState_, 0, 0, 0))
306     {
307         const char* message = lua_tostring(luaState_, -1);
308         URHO3D_LOGERRORF("Execute Lua file failed: %s", message);
309         lua_pop(luaState_, 1);
310         return false;
311     }
312 
313     return true;
314 }
315 
ExecuteFunction(const String & functionName)316 bool LuaScript::ExecuteFunction(const String& functionName)
317 {
318     LuaFunction* function = GetFunction(functionName);
319     return function && function->BeginCall() && function->EndCall();
320 }
321 
SetExecuteConsoleCommands(bool enable)322 void LuaScript::SetExecuteConsoleCommands(bool enable)
323 {
324     if (enable == executeConsoleCommands_)
325         return;
326 
327     executeConsoleCommands_ = enable;
328     if (enable)
329         SubscribeToEvent(E_CONSOLECOMMAND, URHO3D_HANDLER(LuaScript, HandleConsoleCommand));
330     else
331         UnsubscribeFromEvent(E_CONSOLECOMMAND);
332 }
333 
RegisterLoader()334 void LuaScript::RegisterLoader()
335 {
336     // Get package.loaders table
337     lua_getglobal(luaState_, "package");
338     lua_getfield(luaState_, -1, "loaders");
339 
340     // Add LuaScript::Loader to the end of the table
341     lua_pushinteger(luaState_, lua_objlen(luaState_, -1) + 1);
342     lua_pushcfunction(luaState_, &LuaScript::Loader);
343     lua_settable(luaState_, -3);
344     lua_pop(luaState_, 2);
345 }
346 
AtPanic(lua_State * L)347 int LuaScript::AtPanic(lua_State* L)
348 {
349     String errorMessage = luaL_checkstring(L, -1);
350     URHO3D_LOGERROR("Lua error: Error message = '" + errorMessage + "'");
351     lua_pop(L, 1);
352     return 0;
353 }
354 
Loader(lua_State * L)355 int LuaScript::Loader(lua_State* L)
356 {
357     // Get module name
358     String fileName(luaL_checkstring(L, 1));
359 
360 #ifdef URHO3D_LUA_RAW_SCRIPT_LOADER
361     // First attempt to load lua script file from the file system
362     // Attempt to load .luc file first, then fall back to .lua
363     LuaScript* lua = ::GetContext(L)->GetSubsystem<LuaScript>();
364     if (lua->LoadRawFile(fileName + ".luc") || lua->LoadRawFile(fileName + ".lua"))
365         return 1;
366 #endif
367 
368     ResourceCache* cache = ::GetContext(L)->GetSubsystem<ResourceCache>();
369 
370     // Attempt to get .luc file first
371     LuaFile* lucFile = cache->GetResource<LuaFile>(fileName + ".luc", false);
372     if (lucFile)
373         return lucFile->LoadChunk(L) ? 1 : 0;
374 
375     // Then try to get .lua file. If this also fails, error is logged and
376     // resource not found event is sent
377     LuaFile* luaFile = cache->GetResource<LuaFile>(fileName + ".lua");
378     if (luaFile)
379         return luaFile->LoadChunk(L) ? 1 : 0;
380 
381     return 0;
382 }
383 
ReplacePrint()384 void LuaScript::ReplacePrint()
385 {
386     static const struct luaL_reg reg[] =
387     {
388         {"print", &LuaScript::Print},
389         {NULL, NULL}
390     };
391 
392     lua_getglobal(luaState_, "_G");
393     luaL_register(luaState_, NULL, reg);
394     lua_pop(luaState_, 1);
395 }
396 
Print(lua_State * L)397 int LuaScript::Print(lua_State* L)
398 {
399     int n = lua_gettop(L);
400     Vector<String> strings((unsigned)n);
401 
402     // Call the tostring function repeatedly for each arguments in the stack
403     lua_getglobal(L, "tostring");
404     for (int i = 1; i <= n; ++i)
405     {
406         lua_pushvalue(L, -1);
407         lua_pushvalue(L, i);
408         lua_call(L, 1, 1);
409         const char* s = lua_tostring(L, -1);
410         lua_pop(L, 1);
411         if (s)
412             strings[i - 1] = s;
413         else
414         {
415             lua_pop(L, 1);
416             return luaL_error(L, LUA_QL("tostring") " failed at index %d to return a string to " LUA_QL("print"), i);
417         }
418     }
419     lua_pop(L, 1);
420 
421     URHO3D_LOGRAWF("%s\n", String::Joined(strings, "    ").CString());
422     return 0;
423 }
424 
GetFunction(int index)425 LuaFunction* LuaScript::GetFunction(int index)
426 {
427     if (!lua_isfunction(luaState_, index))
428         return 0;
429 
430     const void* functionPointer = lua_topointer(luaState_, index);
431     if (!functionPointer)
432         return 0;
433 
434     HashMap<const void*, SharedPtr<LuaFunction> >::Iterator i = functionPointerToFunctionMap_.Find(functionPointer);
435     if (i != functionPointerToFunctionMap_.End())
436         return i->second_;
437 
438     SharedPtr<LuaFunction> function(new LuaFunction(luaState_, index));
439     functionPointerToFunctionMap_[functionPointer] = function;
440 
441     return function;
442 }
443 
GetFunction(const String & functionName,bool silentIfNotFound)444 LuaFunction* LuaScript::GetFunction(const String& functionName, bool silentIfNotFound)
445 {
446     if (!luaState_)
447         return 0;
448 
449     HashMap<String, SharedPtr<LuaFunction> >::Iterator i = functionNameToFunctionMap_.Find(functionName);
450     if (i != functionNameToFunctionMap_.End())
451         return i->second_;
452 
453     SharedPtr<LuaFunction> function;
454     if (PushLuaFunction(luaState_, functionName))
455     {
456         function = GetFunction(-1);
457         functionNameToFunctionMap_[functionName] = function;
458     }
459     else if (!silentIfNotFound)
460         URHO3D_LOGERRORF("%s", lua_tostring(luaState_, -1));
461     lua_pop(luaState_, 1);
462 
463     return function;
464 }
465 
HandlePostUpdate(StringHash eventType,VariantMap & eventData)466 void LuaScript::HandlePostUpdate(StringHash eventType, VariantMap& eventData)
467 {
468     if (coroutineUpdate_ && coroutineUpdate_->BeginCall())
469     {
470         using namespace PostUpdate;
471         float timeStep = eventData[P_TIMESTEP].GetFloat();
472         coroutineUpdate_->PushFloat(timeStep);
473         coroutineUpdate_->EndCall();
474     }
475 
476     // Collect garbage
477     {
478         URHO3D_PROFILE(LuaCollectGarbage);
479         lua_gc(luaState_, LUA_GCCOLLECT, 0);
480     }
481 }
482 
HandleConsoleCommand(StringHash eventType,VariantMap & eventData)483 void LuaScript::HandleConsoleCommand(StringHash eventType, VariantMap& eventData)
484 {
485     using namespace ConsoleCommand;
486     if (eventData[P_ID].GetString() == GetTypeName())
487         ExecuteString(eventData[P_COMMAND].GetString());
488 }
489 
PushLuaFunction(lua_State * L,const String & functionName)490 bool LuaScript::PushLuaFunction(lua_State* L, const String& functionName)
491 {
492     Vector<String> splitNames = functionName.Split('.');
493 
494     String currentName = splitNames.Front();
495     lua_getglobal(L, currentName.CString());
496 
497     if (splitNames.Size() > 1)
498     {
499         for (unsigned i = 0; i < splitNames.Size() - 1; ++i)
500         {
501             if (i)
502             {
503                 currentName = currentName + "." + splitNames[i];
504                 lua_getfield(L, -1, splitNames[i].CString());
505                 lua_replace(L, -2);
506             }
507             if (!lua_istable(L, -1))
508             {
509                 lua_pop(L, 1);
510                 lua_pushstring(L, ("Could not find Lua table: Table name = '" + currentName + "'").CString());
511                 return false;
512             }
513         }
514 
515         currentName = currentName + "." + splitNames.Back();
516         lua_getfield(L, -1, splitNames.Back().CString());
517         lua_replace(L, -2);
518     }
519 
520     if (!lua_isfunction(L, -1))
521     {
522         lua_pop(L, 1);
523         lua_pushstring(L, ("Could not find Lua function: Function name = '" + currentName + "'").CString());
524         return false;
525     }
526 
527     return true;
528 }
529 
RegisterLuaScriptLibrary(Context * context)530 void RegisterLuaScriptLibrary(Context* context)
531 {
532     LuaFile::RegisterObject(context);
533     LuaScriptInstance::RegisterObject(context);
534 }
535 
536 }
537