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