1 /*
2  * Copyright (C) 2006-2019 Christopho, Solarus - http://www.solarus-games.org
3  *
4  * Solarus is free software; you can redistribute it and/or modify
5  * it under the terms of the GNU General Public License as published by
6  * the Free Software Foundation, either version 3 of the License, or
7  * (at your option) any later version.
8  *
9  * Solarus is distributed in the hope that it will be useful,
10  * but WITHOUT ANY WARRANTY; without even the implied warranty of
11  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
12  * GNU General Public License for more details.
13  *
14  * You should have received a copy of the GNU General Public License along
15  * with this program. If not, see <http://www.gnu.org/licenses/>.
16  */
17 #include "solarus/core/Debug.h"
18 #include "solarus/core/Game.h"
19 #include "solarus/core/MainLoop.h"
20 #include "solarus/core/Map.h"
21 #include "solarus/core/System.h"
22 #include "solarus/core/Timer.h"
23 #include "solarus/entities/Entity.h"
24 #include "solarus/hero/CustomState.h"
25 #include "solarus/lua/ExportableToLuaPtr.h"
26 #include "solarus/lua/LuaContext.h"
27 #include "solarus/lua/LuaTools.h"
28 #include <list>
29 #include <sstream>
30 
31 namespace Solarus {
32 
33 /**
34  * Name of the Lua table representing the timer module.
35  */
36 const std::string LuaContext::timer_module_name = "sol.timer";
37 
38 /**
39  * \brief Initializes the timer features provided to Lua.
40  */
register_timer_module()41 void LuaContext::register_timer_module() {
42 
43   // Functions of sol.timer.
44   const std::vector<luaL_Reg> functions = {
45       { "start", timer_api_start },
46       { "stop_all", timer_api_stop_all }
47   };
48 
49   // Methods of the timer type.
50   const std::vector<luaL_Reg> methods = {
51       { "stop", timer_api_stop },
52       { "is_with_sound", timer_api_is_with_sound },
53       { "set_with_sound", timer_api_set_with_sound },
54       { "is_suspended", timer_api_is_suspended },
55       { "set_suspended", timer_api_set_suspended },
56       { "is_suspended_with_map", timer_api_is_suspended_with_map },
57       { "set_suspended_with_map", timer_api_set_suspended_with_map },
58       { "get_remaining_time", timer_api_get_remaining_time },
59       { "set_remaining_time", timer_api_set_remaining_time }
60   };
61 
62   const std::vector<luaL_Reg> metamethods = {
63       { "__gc", userdata_meta_gc },
64       { "__newindex", userdata_meta_newindex_as_table },
65       { "__index", userdata_meta_index_as_table }
66   };
67 
68   register_type(timer_module_name, functions, methods, metamethods);
69 }
70 
71 /**
72  * \brief Returns whether a value is a userdata of type timer.
73  * \param l A Lua context.
74  * \param index An index in the stack.
75  * \return true if the value at this index is a timer.
76  */
is_timer(lua_State * l,int index)77 bool LuaContext::is_timer(lua_State* l, int index) {
78   return is_userdata(l, index, timer_module_name);
79 }
80 
81 /**
82  * \brief Checks that the userdata at the specified index of the stack is a
83  * timer and returns it.
84  * \param l a Lua context
85  * \param index an index in the stack
86  * \return the timer
87  */
check_timer(lua_State * l,int index)88 TimerPtr LuaContext::check_timer(lua_State* l, int index) {
89   return std::static_pointer_cast<Timer>(
90       check_userdata(l, index, timer_module_name)
91   );
92 }
93 
94 /**
95  * \brief Pushes a timer userdata onto the stack.
96  * \param l A Lua context.
97  * \param timer A timer.
98  */
push_timer(lua_State * l,const TimerPtr & timer)99 void LuaContext::push_timer(lua_State* l, const TimerPtr& timer) {
100   push_userdata(l, *timer);
101 }
102 
103 /**
104  * \brief Registers a timer into a context (table or a userdata).
105  * \param timer A timer.
106  * \param context_index Index of the table or userdata in the stack.
107  * \param callback_ref Lua ref to the function to call when the timer finishes.
108  */
add_timer(const TimerPtr & timer,int context_index,const ScopedLuaRef & callback_ref)109 void LuaContext::add_timer(
110     const TimerPtr& timer,
111     int context_index,
112     const ScopedLuaRef& callback_ref
113 ) {
114   ScopedLuaRef context = LuaTools::create_ref(current_l,context_index);
115 
116   Debug::execute_if_debug([&] {
117     // Sanity check: check the uniqueness of the ref.
118     for (const auto& kvp: timers) {
119       if (kvp.second.callback_ref.get() == callback_ref.get()) {
120         std::ostringstream oss;
121         oss << "Callback ref " << callback_ref.get()
122             << " is already used by a timer (duplicate luaL_unref?)";
123         Debug::die(oss.str());
124       }
125     }
126   });
127 
128   Debug::check_assertion(timers.find(timer) == timers.end(),
129       "Duplicate timer in the system");
130 
131   timers[timer].callback_ref = callback_ref;
132   timers[timer].context = context;
133 
134   Game* game = main_loop.get_game();
135   if (game != nullptr) {
136     // We are during a game: depending on the timer's context,
137     // suspend the timer or not.
138     if (is_map(current_l, context_index)
139         || is_entity(current_l, context_index)
140         || is_state(current_l, context_index)
141         || is_item(current_l, context_index)) {
142 
143       timer->set_suspended_with_map(true);
144 
145       bool initially_suspended = false;
146       // By default, we want the timer to be automatically suspended when a
147       // camera movement, a dialog or the pause menu starts.
148       if (!is_entity(current_l, context_index) && !is_state(current_l, context_index)) {
149 
150         // But in the initial state, we override that rule.
151         // We initially suspend the timer only during a dialog.
152         // In particular, we don't want to suspend timers created during a
153         // camera movement.
154         // This would be very painful for users.
155         initially_suspended = game->is_dialog_enabled();
156       }
157       else {
158         // Entities are more complex: they also get suspended when disabled.
159         if (is_entity(current_l, context_index)) {
160           EntityPtr entity = check_entity(current_l, context_index);
161           initially_suspended = entity->is_suspended() || !entity->is_enabled();
162         } else {  // State.
163           std::shared_ptr<CustomState> state = check_state(current_l, context_index);
164           initially_suspended = state->is_suspended();
165         }
166       }
167 
168       timer->set_suspended(initially_suspended);
169     }
170   }
171 }
172 
173 /**
174  * \brief Unregisters a timer associated to a context.
175  *
176  * This function can be called safely even while iterating on the timer list.
177  *
178  * \param timer A timer.
179  */
remove_timer(const TimerPtr & timer)180 void LuaContext::remove_timer(const TimerPtr& timer) {
181   if (timers.find(timer) != timers.end()) {
182     timers[timer].callback_ref.clear();
183     timers_to_remove.push_back(timer);
184   }
185 }
186 
187 /**
188  * \brief Unregisters all timers associated to a context.
189  *
190  * This function can be called safely even while iterating on the timer list.
191  *
192  * \param context_index Index of a table or userdata containing timers.
193  */
remove_timers(int context_index)194 void LuaContext::remove_timers(int context_index) {
195   for (auto& kvp: timers) {
196     const TimerPtr& timer = kvp.first;
197     if (kvp.second.context.equals(current_l,context_index)) {
198       kvp.second.callback_ref.clear();
199       timers_to_remove.push_back(timer);
200     }
201   }
202 }
203 
204 /**
205  * \brief Destroys immediately all existing timers.
206  */
destroy_timers()207 void LuaContext::destroy_timers() {
208   timers.clear();
209 }
210 
211 /**
212  * \brief Updates all timers currently running for this script.
213  */
update_timers()214 void LuaContext::update_timers() {
215 
216   // Update all timers.
217   for (const auto& kvp: timers) {
218 
219     const TimerPtr& timer = kvp.first;
220     const ScopedLuaRef& callback_ref = kvp.second.callback_ref;
221     if (!callback_ref.is_empty()) {
222       // The timer is not being removed: update it.
223       timer->update();
224       if (timer->is_finished()) {
225         do_timer_callback(timer);
226       }
227     }
228   }
229 
230   // Destroy the ones that should be removed.
231   for (const TimerPtr& timer: timers_to_remove) {
232 
233     const auto& it = timers.find(timer);
234     if (it != timers.end()) {
235       timers.erase(it);
236 
237       Debug::check_assertion(timers.find(timer) == timers.end(),
238           "Failed to remove timer");
239     }
240   }
241   timers_to_remove.clear();
242 }
243 
244 /**
245  * \brief This function is called when the game is being suspended or resumed.
246  * \param suspended \c true if the game is suspended, false if it is resumed.
247  */
notify_timers_map_suspended(bool suspended)248 void LuaContext::notify_timers_map_suspended(bool suspended) {
249 
250   for (const auto& kvp: timers) {
251     const TimerPtr& timer = kvp.first;
252     const ScopedLuaRef& context = kvp.second.context;
253     context.push(current_l);
254     if (is_entity(current_l, -1) || is_state(current_l, -1)) {
255       // Already handled by the entity.
256       lua_pop(current_l, 1);
257       continue;
258     }
259     if (timer->is_suspended_with_map()) {
260       timer->set_suspended(suspended);
261     }
262     lua_pop(current_l, 1);
263   }
264 }
265 
266 /**
267  * \brief Suspends or resumes the timers attached to a map entity or to its state.
268  *
269  * This takes into account the Timer::is_suspended_with_map() property.
270  *
271  * \param entity A map entity.
272  * \param suspended \c true to suspend its timers
273  * (unless Timer::is_suspended_with_map() is false), \c false to resume them.
274  */
set_entity_timers_suspended_as_map(Entity & entity,bool suspended)275 void LuaContext::set_entity_timers_suspended_as_map(
276     Entity& entity, bool suspended
277 ) {
278   for (const auto& kvp: timers) {
279     const TimerPtr& timer = kvp.first;
280     if (kvp.second.context == entity ||
281         (entity.get_state() && kvp.second.context == *entity.get_state().get())) {
282 
283       if (!suspended) {
284         timer->set_suspended(false);
285       }
286       else {
287         // Suspend timers except the ones that ignore the map being suspended.
288         if (timer->is_suspended_with_map()) {
289           timer->set_suspended(true);
290         }
291       }
292     }
293   }
294 }
295 
296 /**
297  * \brief Executes the callback of a timer.
298  *
299  * Then, if the callback returns \c true, the timer is rescheduled,
300  * otherwise it is discarded.
301  *
302  * Does nothing if the timer is already finished.
303  *
304  * \param timer The timer to execute.
305  */
do_timer_callback(const TimerPtr & timer)306 void LuaContext::do_timer_callback(const TimerPtr& timer) {
307   Debug::check_assertion(timer->is_finished(), "This timer is still running");
308 
309   auto it = timers.find(timer);
310   if (it != timers.end() &&
311       !it->second.callback_ref.is_empty()) {
312     ScopedLuaRef& callback_ref = it->second.callback_ref;
313     run_on_main([&,timer](lua_State* l){ //Here l shadow previous l on purpose, capture timer by value to increase ref count
314       if(callback_ref.is_empty()) {
315         return; //Ref might be cleared meanwhile
316       }
317       callback_ref.push(l);
318       const bool success = LuaTools::call_function(l,0,1,"timer callback");
319 
320       bool repeat = false;
321       int interval = timer->get_duration();
322       if (success) {
323 
324         if (lua_isnumber(l, -1)) {
325           interval = lua_tointeger(l, -1);
326           if (interval < 0) {
327             std::ostringstream oss;
328             oss << "Invalid timer delay: " + oss.str();
329             Debug::error(oss.str());
330           }
331           else {
332             repeat = true;
333           }
334         }
335         else if (lua_isboolean(l, -1)) {
336           repeat = lua_toboolean(l, -1);
337         }
338         lua_pop(l, 1);
339       }
340 
341       if (repeat) {
342         // The callback returned true: reschedule the timer.
343         timer->set_duration(interval);
344         timer->set_expiration_date(timer->get_expiration_date() + interval);
345         if (timer->is_finished()) {
346           // Already finished: this is possible if the duration is smaller than
347           // the main loop stepsize.
348           do_timer_callback(timer);
349         }
350       }
351       else {
352         callback_ref.clear();
353         timers_to_remove.push_back(timer);
354       }
355     });
356   }
357 }
358 
359 /**
360  * \brief Implementation of sol.timer.start().
361  * \param l the Lua context that is calling this function
362  * \return number of values to return to Lua
363  */
timer_api_start(lua_State * l)364 int LuaContext::timer_api_start(lua_State *l) {
365 
366   return state_boundary_handle(l, [&] {
367     // Parameters: [context] delay callback.
368     LuaContext& lua_context = get();
369 
370     bool use_default_context = false;
371     if (lua_type(l, 1) == LUA_TNUMBER) {
372       use_default_context = true;
373     }
374     else {
375       // The first parameter is the context.
376       if (!is_main(l, 1) &&
377           !is_menu(l, 1) &&
378           !is_game(l, 1) &&
379           !is_item(l, 1) &&
380           !is_map(l, 1) &&
381           !is_entity(l, 1) &&
382           !is_state(l, 1)
383       ) {
384         // Show an error message without raising a Lua error
385         // because this problem was not detected correctly before 1.6
386         // and a lot of existing games have it.
387         // We can survive this by just using a default context as fallback.
388         std::ostringstream oss;
389         oss << "(Thread " << l <<  ")";
390         std::string message = "bad argument #1 to sol.timer.start (game, item, map, entity, menu or sol.main expected, got " +
391             LuaTools::get_type_name(l, 1) + "), will use a default context instead";
392         lua_pushcfunction(l, l_backtrace);
393         push_string(l, message);
394         LuaTools::call_function(l, 1, 1, "traceback");
395         std::string backtrace = LuaTools::check_string(l, -1);
396         lua_pop(l, 1);
397         Debug::error(backtrace + "\n" + oss.str());
398 
399         lua_remove(l, 1);
400         use_default_context = true;
401       }
402     }
403 
404     if (use_default_context) {
405       // Set a default context:
406       // - during a game: the current map,
407       // - outside a game: sol.main.
408 
409       Game* game = lua_context.get_main_loop().get_game();
410       if (game != nullptr && game->has_current_map()) {
411         push_map(l, game->get_current_map());
412       }
413       else {
414         push_main(l);
415       }
416       lua_insert(l, 1);
417     }
418     // Now the first parameter is the context.
419 
420     uint32_t delay = uint32_t(LuaTools::check_int(l, 2));
421     const ScopedLuaRef& callback_ref = LuaTools::check_function(l, 3);
422 
423     // Create the timer.
424     TimerPtr timer = std::make_shared<Timer>(delay);
425     lua_context.add_timer(timer, 1, callback_ref);
426 
427     if (delay == 0) {
428       // The delay is zero: call the function right now.
429       lua_context.do_timer_callback(timer);
430     }
431 
432     push_timer(l, timer);
433 
434     return 1;
435   });
436 }
437 
438 /**
439  * \brief Implementation of timer:stop().
440  * \param l the Lua context that is calling this function
441  * \return number of values to return to Lua
442  */
timer_api_stop(lua_State * l)443 int LuaContext::timer_api_stop(lua_State* l) {
444 
445   return state_boundary_handle(l, [&] {
446     LuaContext& lua_context = get();
447     const TimerPtr& timer = check_timer(l, 1);
448     lua_context.remove_timer(timer);
449 
450     return 0;
451   });
452 }
453 
454 /**
455  * \brief Implementation of sol.timer.stop_all().
456  * \param l the Lua context that is calling this function
457  * \return number of values to return to Lua
458  */
timer_api_stop_all(lua_State * l)459 int LuaContext::timer_api_stop_all(lua_State* l) {
460 
461   return state_boundary_handle(l, [&] {
462     if (lua_type(l, 1) != LUA_TTABLE
463         && lua_type(l, 1) != LUA_TUSERDATA) {
464       LuaTools::type_error(l, 1, "table or userdata");
465     }
466 
467     get().remove_timers(1);
468 
469     return 0;
470   });
471 }
472 
473 /**
474  * \brief Implementation of timer:is_with_sound().
475  * \param l The Lua context that is calling this function.
476  * \return Number of values to return to Lua.
477  */
timer_api_is_with_sound(lua_State * l)478 int LuaContext::timer_api_is_with_sound(lua_State* l) {
479 
480   return state_boundary_handle(l, [&] {
481     const TimerPtr& timer = check_timer(l, 1);
482 
483     lua_pushboolean(l, timer->is_with_sound());
484     return 1;
485   });
486 }
487 
488 /**
489  * \brief Implementation of timer:set_with_sound().
490  * \param l The Lua context that is calling this function.
491  * \return Number of values to return to Lua.
492  */
timer_api_set_with_sound(lua_State * l)493 int LuaContext::timer_api_set_with_sound(lua_State* l) {
494 
495   return state_boundary_handle(l, [&] {
496     const TimerPtr& timer = check_timer(l, 1);
497     bool with_sound = LuaTools::opt_boolean(l, 2, true);
498 
499     timer->set_with_sound(with_sound);
500 
501     return 0;
502   });
503 }
504 
505 /**
506  * \brief Implementation of timer:is_suspended().
507  * \param l The Lua context that is calling this function.
508  * \return Number of values to return to Lua.
509  */
timer_api_is_suspended(lua_State * l)510 int LuaContext::timer_api_is_suspended(lua_State* l) {
511 
512   return state_boundary_handle(l, [&] {
513     const TimerPtr& timer = check_timer(l, 1);
514 
515     lua_pushboolean(l, timer->is_suspended());
516     return 1;
517   });
518 }
519 
520 /**
521  * \brief Implementation of timer:set_suspended().
522  * \param l The Lua context that is calling this function.
523  * \return Number of values to return to Lua.
524  */
timer_api_set_suspended(lua_State * l)525 int LuaContext::timer_api_set_suspended(lua_State* l) {
526 
527   return state_boundary_handle(l, [&] {
528     const TimerPtr& timer = check_timer(l, 1);
529     bool suspended = LuaTools::opt_boolean(l, 2, true);
530 
531     timer->set_suspended(suspended);
532 
533     return 0;
534   });
535 }
536 
537 /**
538  * \brief Implementation of timer:is_suspended_with_map().
539  * \param l The Lua context that is calling this function.
540  * \return Number of values to return to Lua.
541  */
timer_api_is_suspended_with_map(lua_State * l)542 int LuaContext::timer_api_is_suspended_with_map(lua_State* l) {
543 
544   return state_boundary_handle(l, [&] {
545     const TimerPtr& timer = check_timer(l, 1);
546 
547     lua_pushboolean(l, timer->is_suspended_with_map());
548     return 1;
549   });
550 }
551 
552 /**
553  * \brief Implementation of timer:set_suspended_with_map().
554  * \param l The Lua context that is calling this function.
555  * \return Number of values to return to Lua.
556  */
timer_api_set_suspended_with_map(lua_State * l)557 int LuaContext::timer_api_set_suspended_with_map(lua_State* l) {
558 
559   return state_boundary_handle(l, [&] {
560     LuaContext& lua_context = get();
561 
562     const TimerPtr& timer = check_timer(l, 1);
563     bool suspended_with_map = LuaTools::opt_boolean(l, 2, true);
564 
565     timer->set_suspended_with_map(suspended_with_map);
566 
567     Game* game = lua_context.get_main_loop().get_game();
568     if (game != nullptr &&
569         game->has_current_map() &&
570         suspended_with_map) {
571       // If the game is running, suspend/resume the timer like the map.
572       timer->set_suspended(game->get_current_map().is_suspended());
573     }
574 
575     return 0;
576   });
577 }
578 
579 /**
580  * \brief Implementation of timer:get_remaining_time().
581  * \param l The Lua context that is calling this function.
582  * \return Number of values to return to Lua.
583  */
timer_api_get_remaining_time(lua_State * l)584 int LuaContext::timer_api_get_remaining_time(lua_State* l) {
585 
586   return state_boundary_handle(l, [&] {
587     const TimerPtr& timer = check_timer(l, 1);
588 
589     LuaContext& lua_context = get();
590     const auto it = lua_context.timers.find(timer);
591     if (it == lua_context.timers.end() ||
592         it->second.callback_ref.is_empty()) {
593       // This timer is already finished or was canceled.
594       lua_pushinteger(l, 0);
595     }
596     else {
597       int remaining_time = (int) timer->get_expiration_date() - (int) System::now();
598       if (remaining_time < 0) {
599         remaining_time = 0;
600       }
601       lua_pushinteger(l, remaining_time);
602     }
603     return 1;
604   });
605 }
606 
607 /**
608  * \brief Implementation of timer:set_remaining_time().
609  * \param l The Lua context that is calling this function.
610  * \return Number of values to return to Lua.
611  */
timer_api_set_remaining_time(lua_State * l)612 int LuaContext::timer_api_set_remaining_time(lua_State* l) {
613 
614   return state_boundary_handle(l, [&] {
615     const TimerPtr& timer = check_timer(l, 1);
616     uint32_t remaining_time = LuaTools::check_int(l, 2);
617 
618     LuaContext& lua_context = get();
619     const auto it = lua_context.timers.find(timer);
620     if (it != lua_context.timers.end() &&
621         !it->second.callback_ref.is_empty()) {
622       // The timer is still active.
623       const uint32_t now = System::now();
624       const uint32_t expiration_date = now + remaining_time;
625       timer->set_expiration_date(expiration_date);
626       if (now >= expiration_date) {
627         // Execute the callback now.
628         lua_context.do_timer_callback(timer);
629       }
630     }
631 
632     return 0;
633   });
634 }
635 
636 }
637 
638