8 comments

[ 0.29 ms ] story [ 30.9 ms ] thread
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?
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.

(comment deleted)
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?

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.

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!
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
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.