
Show HN: Lua Prototype of Elm's Time-Travelling Debugger - meric
https://github.com/meric/l2l/tree/master/samples/sample09
======
domodomo
I've been planning on learning Love2D / Lua soon, and this looks amazing.

Have you shared it on the Love2D forums yet?

Would it work well for a use case like say, debugging behavior on collision?
Like say I fall through a platform, rewind, change a variable, play forwards,
fall through, rewind again, etc?

~~~
meric
Thanks. I think it would definitely work if the collision code is within the
same module, in the same way the current physics, where mario is pulled down
by gravity down to the ground and no more. If it's calling out to
love.physics, I suspect there will be issues, and I think there can be
workarounds, but I cannot say so for 100% until I've tried it thoroughly
myself.

I built this prototype to see if it works, I'll be able to take it further.
Currently, I don't think I'll be able to use it for development myself because
it's API isn't well designed. I built the minimum to emulate Elm's mario
example, but a lot more work needs to be done to be used in a proper game
where file saving, loading and calling out to the external framework is
involved.

------
versteegen
Incidentally, I'm planning to implement a scripting language built on top of
luajit for a game engine, and make an attempt to include time-travelling
debugging. My main inspiration is Mozilla's rr rather than Elm. My plan is to
make heavy use of the engine's existing save/load system, and save a
checkpoint every few seconds. So no rerunning from the beginning of time if
you edit a function. The really tricky part will be saving and resuming the
state of running functions in luajit (certainly possible using source
transformations, but not sure it'll be efficient). But anyway, thanks for
this, I'll study it in detail!

~~~
meric
Hope this bit of code will be useful to you! You can use it to store the state
of a function and restore it later.

    
    
        local function getupvalues(f)
            local state = {}
            local i = 1
            while true do
                local n, v = debug.getupvalue(f, i)
                if not n then break end
                table.insert(state, v)
                i = i + 1
            end
            return state
        end
    
        local function setupvalues(f, state)
            for i, value in ipairs(state) do
                debug.setupvalue(f, i, value)
            end
            return state
        end

~~~
versteegen
Thanks. Though I was thinking of how to also save/restore the program counter.

There is (or was, a few months ago) actually a bug in luajit that
debug.setupvalue doesn't work on JIT compiled functions. From my reading of
the source, it's not a major problem to fix it, and someone mentioned that
they might do so.

------
chii
That's really cool. How is the IO of rendering "recorded"? I don't know lua
well enough to tell - i suppose it must be that each pixel color is being
recorded, and the replay simply blit it back?

~~~
meric
Thanks! As long as the screen can be described entirely by the IO rendering
functions and the state within the recorded Lua data structures, there's no
need to record each pixel color - simply replay the IO rendering functions in
the same order using the same Lua state.

The prototype takes advantage of the fact during every frame the screen is
reset, and is rendered from scratch. The prototype's "time travelling" works
by going back to the very beginning of the program, and replaying every single
step. Even if the screen isn't reset every frame, as long as the program can
reset the screen when it goes back to the beginning, it will be able to handle
replaying the graphics IO.

With "additive" IO functions, such as `print`, which "adds" rather than
"resets" the terminal on each "frame", I think there will be a need to replace
those functions with an alternate set that does IO through a medium where past
additions to the output can be modified. e.g. using ncurses instead of
terminal, to show program output.

