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