Crash with sol.main.load_file

Started by linky, August 05, 2014, 04:16:57 PM

Previous topic - Next topic
Hi,

I need some help because i am fighting with an ununderstandable problem that systematically crash solarus.

I was trying to build the elements of the hud (rupee, heart...) to understand how to do it and without any problem so far. But doing my test, i realized that when the game restart because the hero died, the hud didn't come up again.

So i look in the script of solarus DX and understood that i have to put something in a  "game:on_started" function that is called each time the game restart after the death of the hero, that will reinitialize the hud.

So i try to do so by copying the way it is done in DX and there the problem begins.

So far to include module files in the main.lua file (for example for the hud.lua) i was just using "require(filename)" that works well. But i have seen that to declare the "game:on_started" function, another way of calling a module is used with "load_file" which is an adapted version of lua "loadfile" command for solarus. So i search a lua help to try to understand this command and find this :


-- just load the module without runing it
sol.main.load_file(filename)
-- load the module and run
sol.main.load_file(filename)(some arguments)


if i have well understood the avantage of "require" is that it loads the modules only once (so preserves memory), but it doesn't seems to be able to send arguments to module's function. To be sure I try the following :


require(filename)(some arguments)


but it doesn't pass the arguments. So I then try to use the "load_file" instead as shown in the DX's script and do exactly as it (at least i think so). And then solarus begins to crash all the time.

As i have put some code inside "game:on_started" i try to undertand what part was causing the crash by removing things little by little. But it was complicated because the crash occured or not depending if i add or remove some instructions somewhere in this module or in another one called by "game:on_started", even sometimes on very simple command like "print". So it wasn't easy to localize which part was the problem because it doesn't crash with any commmand but only with some of them.

For example i have seen that i have timer called in one of my module, and i discover that if i comment it, then there's no crash (but i need this timer).

Finally i make a copy of the project and take everything out (all modules and even clean the main). Now i have a mini project with just 2 files: main.lua and a module called play.lua in the data folder. And it continue to crah my solarus (version 1.2 or 1.21).

To be sure there was not a little part of my code hidden somewhere (or in the map definition for example) that gives instability to the program, i have took back the "simple_quest" example proposed in solarus 1.2 zip file you can download on solarus site and that you try at the begining to understand the basics of solarus.

It is just composed of one map inside a house where you almost can't do anything. It was the first example i begun with, and i knew it works well. So i just took this data folder and just change the "main.lua" by mine and add my problematic module "play.lua" and then... it crash solarus. I really didn't changed anything else than this 2 files. Here they are :


-- main.lua
function sol.main:on_started()

    game = sol.game.load("save1.dat")
    game:set_max_life(12)

    sol.main.load_file("play")(game)

    game:start()

end



-- play.lua

local game = ...

function game:on_started()

    -- this simple timer that does nothing make solarus crash
    sol.timer.start(100, function()  end)
     
end


For me, this very simple code crash solarus with nothing log in the error.txt file, but if i comment the call to "sol.main.load_file" the quest start on without problem.

(In my real code the game create process and game:start() are not in the main.lua but in a game_manager.lua, i just put it there to simplify at the maximum).

So probably i'm not using the command sol.main.load_file("play")(game) like it is neccessary because in DX it works without problem. But i don't see what i do wrong by comparing with the use of it in DX.

I must precise that the timer function i use in method "game:on_started" is just an example. It is one of the function that i have localized to cause the crash in some of my modules called by "game:on_started".

But in my code, even a simple "print" function" make a crash (it depends where it is put). This is not the case in this simple version because i have removed all the other modules that "game:on_started" is supposed to call when the game restart.

So i don't think the problem comes from the timer itself as the problem occurs with other functions. If i put this same timer command in the "main.lua" it runs without problem, it is only inside the "play.lua" file that it makes crash. In my real code, the timer is set in another module called by "game:on_started" so that the crash wasn't localized directly in the play module although the problem seems to come from there.

In "main.lua" i also try another variant used in DX to call load_file, but it crash the same :


-- main.lua
function sol.main:on_started()

    game = sol.game.load("save1.dat")
    game:set_max_life(12)

    local mytest = sol.main.load_file("play")
    mytest(game)

    game:start()

end


I wish to use "require" instead because it never make crash solarus, but apparently it is not possible because it can't parse the argument "game" to the module "game:on_started", although it really needs it.

So could you please help me as i don't know what to do more.

If you don't see a big big stupid error of beginer from my code that will be evident to your expert eyes, could you then try to do like me by taking your "simple_quest" given with the solarus 1.2 engine and put those two files (see attachments) in the data folder to see what happens and if it's normal.

Thanks a lot

Thanks for the detailed bug report. I will try to reproduce the crash and fix it.

About require not accepting arguments, this is because the file included is executed only once (the first time it is required) and without arguments. Next times, require() does not execute the code again, is only returns whatever the file returned the first time. The usual way is to return a table that contains functions.

If you need arguments (and we often do!), here is the trick: wrap all the content of your file in a big function called for example create() or build(). This big function takes the necessary arguments. And your file returns a table that contains this big function.
This is what I do all the time, like in the rupee counter code I posted in the other topic. Or also here: https://github.com/christopho/zelda_mercuris_chest/blob/master/data/scripts/dialog_box.lua
The sol.main.load_file() calls in ZSDX are only there for historical reasons but I don't like them. I don't like them not only because the same file is parsed and executed several times, but also because sol.main.load_file("rupee_counter")(game) is an ulgy line :)

August 05, 2014, 07:19:25 PM #2 Last Edit: August 05, 2014, 07:23:45 PM by linky
Thank you for your quick answer,

In fact I already used this trick because it is used all the time especially to pass "game" to some functions who need it and it works well :


-- in the module play :

local init = {}

   function init:create(game)   
       hud_initialize(game)   
   end   

return init

-- in main.lua :

init = require("play")
init:create(game)


But i'm not sure it will be sufficient in this case, because it is not the same kind of "arguments passing" we need and this is not a custom function. I have to add an event "on_started" to the "game" table that is already defined by the engine :


function game:on_started()

    hud_initialize(self)   

end   


This is the code that will be called each time the game start, so i can't call it "whatyouwant:on_started()" it has to be called "game:on_started()" or a variable name that will be replace by the table "game" when solarus will run, and i don't think "require" can do it.

I thought it was why in DX you use the other "load_file" function that allow to pass directly arguments (here it is the "game" table) to the module even to the name of the function, thanks to the first line you use "local game = ..." that receive the argument passed by "load_file" :


local game = ...

      function game:on_started()

      end


I think this is a different way of loading the files than "require", but unfortunately i can't manage to make sol.main.load_file works without crash.

Also i can't put simply this function in the main.lua as the table "game" will not be defined at this stage and the compilator is not happy. So this function has to be in a module.

August 05, 2014, 08:10:17 PM #3 Last Edit: August 05, 2014, 08:17:03 PM by vlag
I don't really think that it will work since the same code on ZSDX works fine, but if removing the timer from play.lua doesn't make the game crash, could you try to return a boolean from the timer function ?

Something like
sol.timer.start(100, function() return false end)

Thank you vlag for your help,

When you say that it will not work, are you speaking of the method using "sol.main.load_file" or using "require" ?

In ZSDX the module (play_game.lua) that contains the function "game:on_started" is loaded (in main.lua) using the "sol.main.load_file" method as i am trying to do without sucess.

I try to put a return false in the timer as you propose, but it still crash. But i don't think the problem is with the timer, because the crash can occured with other functions, it was just an example to show the problem. Anyway in my complete code the timer callback function is not empty (this serve to check if the rupee money display has changed).

If some day you have time, you can try to see if you can reproduce the crash by using the two files in attchaments (main.lua and play.lua) and put them in the data folder of the "sample_quest" in the zip file of solarus 1.2 engine.

August 05, 2014, 11:01:53 PM #5 Last Edit: August 05, 2014, 11:07:15 PM by linky
I have made some more tests to include other part of zsdx that i didn't use for now on, like the dialog box which use "sol.main.load_file" to include its modules and it works fine. So the problem of my situation doesn't come specifically from the command "sol.main.load_file".

Also i have said in previous post that the crash occurs when using a timer or a print function, so i was not sure if the problem was coming only from the timer.

But i have now remenbered that the print function i used is a custom one, in which i have added some weeks before a timer just to erase the text printed after some seconds, because it was more easy for me when debuging. So i try to removed the timer in my custom print function and solarus don't crash.

So now i can be more precise : this is apparently the sol.timer.start (whatever callback function you put inside) that crash solarus when used in a module called this way :

in the main.lua

sol.main.load_file("play.lua")(game)


in the module play.lua

local game = ...

function game:on_started()
 
  sol.timer.start(1000, function() whateveryouwant end)
 
end 


This happens even if the timer is not directly called by game:on_started but by a another function that is called by game:on_started (even in another module).

but what is strange is that the crash happens only if the name of the function is game:on_started, if i use game:on_finished or game:whateverelse() it doesn't crash.

So it doesn't make much sens for me and i don't know if it is a bug, or if i'm doing something wrong.

You're not doing things wrong. There seems to be a bug in the C++ code with timers indeed. I will investigate this today if I find time.

About require vs load_file, here is how to define your function game:on_started from your required file:

-- in the module play :

local init = {}

function init:create(game)   
  hud_initialize(game)   

  function game:on_started()
    -- stuff
  end

end   

return init

-- in main.lua :

init = require("play")
init:create(game)


Remember that a function definition is nothing more than an assignment in Lua. Assigning a value (of type function) to a variable.
You can even write it as an assignment if you find it more clear:


-- in the module play :

local init = {}

function init:create(game)   
  hud_initialize(game)   

  game.on_started = function(game)
    -- stuff
  end

end   

return init

-- in main.lua :

init = require("play")
init:create(game)

August 06, 2014, 07:02:55 PM #7 Last Edit: August 06, 2014, 07:06:19 PM by linky
I didn't imagine it was possible to do that  :). The second code is effectively more clear for me.

Thank you, now i know 2 ways to load a module, but even if the load_file method is ugly as you say, i find it more simple to code (and understand) than the tricky method using "require".

Anyway even with this "require" method to load "play.lua", the timer still crash the same way. So my problem is not linked to the module's loading as i first believed  but to the timer when it is called by game:on_started.


Finally i think i have found from where comes the crash. I simply add a context to the timer. The doc says it is optional so i never used one so far, but with a context in the timer i have no more crash. I have choosen "game" even if i'm not sure this is the best choice in this case, but it works :


sol.timer.start(game, 1000, function() end)


So what happened is maybe that the context to which the timer was linked by default (as i don't gave one before) is cleared, or something like that, when the game restart and then the timer having no more context, goes to crash. It's why this crash occured only when the timer is put in the game:on_started() function and nowhere else.


I wonder if it's possible to rename the post : "crash with timer" or something like that, now that we know the problem has nothing to do with the "sol.main.load_file" as i first believed. It would be more clear don't you think ?

I fixed the crash. You are right, there was a crash because the default context was not correctly chosen by the engine.
The default context of a timer is the map if a game is running. and sol.main otherwise.
In game:on_started(), the game is started but not really running yet: the first map is not loaded. The bug was here.