1 2
14 15
Sand
He/Him
Editor, Player (167)
Joined: 6/26/2018
Posts: 237
YoshiRulz wrote:
CPP pushed a fix for pcall, so please try again with a new dev build.
That's great. A dev build after 0572266 works for me. Thanks for your help. It would have taken me much longer to find it on my own. Interesting that the cause was similar to JPC-RR builtin functions returning on the wrong thread. In that case, it affected every return of a builtin function, not just exceptions. So in one sense it was easier to notice. But also, JPC-RR only runs one Lua script at a time, rather than running each script in its own Lua thread, so it would only happen if you actually created new coroutines yourself, which was not the case here.
Darth_Marios
He/Him
Joined: 5/11/2015
Posts: 115
Hi. I'm still trying to make some TAS vs TAS. Since i found out that manual inputs both players or using tastudio (too much mouse playing kill my wrist) is very tedious, im looking in lua scripts to choreograph both players. What i asked is some tips to shorten the element of the scripts, because it took forever typing everytime emu.frameadvance or joypad.set (for both players) Ok, to advance some frames, we can use ' for i= ' instead writing emu.frameadvance the numbers of time needed, but the controls are too long, such as joypad.set({ ["P1 Button 1"] = true }) I'd like something such as what macrolua did in the past, but im sure its not possible to mimic it with a simple script (it use various lua scripts and honestly its way beyond my scripting skills)
Editor, Expert player (2548)
🇩🇪 Germany
Joined: 5/15/2007
Posts: 4120
Location: 🇩🇪 Germany
Using this short script with Super Mario Land
memory.usememorydomain("System Bus")

event.on_bus_read(function(addr, val, flags)
	print("TEST")
end, 0xFFEA)

while true do	
	emu.frameadvance()
end
On Bizhawk 2.3.1, it prints every frame but on Bizhawk 2.8 and Bizhawk 2.9.1, nothing gets printed. Am I missing something or is it broken?
Post subject: Lua/Fennel polyglot
Sand
He/Him
Editor, Player (167)
Joined: 6/26/2018
Posts: 237
As you may know from Post #535321, I've been experimenting with writing scripts for BizHawk in Fennel, a lisp programming language based on Lua. Fennel code has to be compiled to Lua before BizHawk can run it. To do that, I have been dividing programs into two files: a main ".fnl", written in Fennel, that has the actual code; and a stub ".lua" file that compiles and runs the .fnl file. You load the .lua file in BizHawk, then the .lua file runs the .fnl file. For example, I might have a "script.fnl" that contains the code of the script:
Language: fennel

(console.log "Hello Fennel") (console.log (string.format "ROM name: %q" (gameinfo.getromname))) (local fennel (require :fennel)) ;; for fennel.view (console.log "joypad" (fennel.view (joypad.get) {:one-line? true}))
And then a "script.lua" stub that executes the main Fennel file:
Language: lua

require("fennel").install().dofile("script.fnl")
The "fennel" module comes from fennel.lua, which you just have to install in the directory alongside your scripts. The two-file approach works fine. But I found a way to get the same effect with just one file. The file is simultaneously a Lua program and a Fennel program; i.e., a polyglot. It looks like this:
Language: lua

;; return require("fennel").install().eval([==[ (console.log "Hello Fennel") (console.log (string.format "ROM name: %q" (gameinfo.getromname))) (local fennel (require :fennel)) ;; for fennel.view (console.log "joypad" (fennel.view (joypad.get) {:one-line? true})) ;; ]==])
The ;; marks a comment in Fennel. So from the point of view of a Fennel interpreter, the first and last lines of the file disappear, leaving only the lisp code in between. In Lua, ; is an empty statement, a no-op separator. So the Lua interpreter sees and executes the require("fennel").install().eval(...). The [==[ marks the beginning of a long literal string that lasts until the ]==] on the last line—encompassing the entire text of the Fennel program. To the Lua interpreter, the Fennel part of the program looks like one long string, which gets passed to the fennel.eval function. It would be nice if only required adding something to the top of the file, not both top and bottom, but I couldn't think of a way to do that. I've found just one problem with the polyglot approach, which is that BizHawk won't let you load a file with a ".fnl" filename extension with the --lua option on the command line. I want to name the program file "script.fnl", so that when I edit it, I get syntax highlighting and automatic indentation appropriate for Fennel. But BizHawk's --lua option only allows ".lua" and ".txt" as filename extensions, and silently ignores files with any other extension. (Except for ".luases", which I don't know what that is.) You can load .fnl files from the "Open Script" UI in the Lua Console; it's just the --lua option that doesn't work. So I have to name the file "script.lua", which adds inconvenience when editing it as Fennel code. So I might stick with the two-file method anyway. If you want to see how the Fennel code compiles to Lua, you can paste it in here.
YoshiRulz
Any
Editor, Emulator Coder
Location: 🇦🇺 Sydney, Australia
Joined: 8/30/2020
Posts: 216
Location: 🇦🇺 Sydney, Australia
.luases is a session, basically a list of scripts to save/restore at once. I've removed the file extension restriction from the command-line.
I contribute to BizHawk as Linux/cross-platform lead, testing and automation lead, and UI designer. This year, I'm experimenting with streaming BizHawk development on Twitch. nope Links to find me elsewhere and to some of my side projects are on my personal site. I will respond on Discord faster than to PMs on this site.
Hey look buddy, I'm an engineer. That means I solve problems. Not problems like "What is software," because that would fall within the purview of your conundrums of philosophy. I solve practical problems. For instance, how am I gonna stop some high-wattage thread-ripping monster of a CPU dead in its tracks? The answer: use code. And if that don't work? Use more code.
Editor, Expert player (2548)
🇩🇪 Germany
Joined: 5/15/2007
Posts: 4120
Location: 🇩🇪 Germany
Playing Mario Pinball Land on Bizhawk, I'm trying to put this hook, which prints an error whenever it triggers.
Language: Lua

local val = { ball = { -- ... collided_with_flipper = false }, collectible = { --... }, cannon = { -- ... }, misc = { --... }, left_flipper = { --... }, right_flipper = { --... } } event.on_bus_exec(function(addr, val, flags) val.ball.collided_with_flipper = true end, 0x08015C1E)
error running function attached by the event OnMemoryExecute
Error message: [string "main"]:221: attempt to index a number value (local 'val')
However, this alternative code works:
Language: Lua

local collided_with_flipper = false event.on_bus_exec(function(addr, val, flags) collided_with_flipper = true end, 0x08015C1E)
Why didn't the first code work?
Sand
He/Him
Editor, Player (167)
Joined: 6/26/2018
Posts: 237
MUGG wrote:
Why didn't the first code work?
It's because you used the name val both as the name of the outer table and as one of the parameters in the anonymous callback function.
local val = {
function(addr, val, flags)
In the environment of the anonymous callback function, val refers to the function parameter, not the outer table you intend. BizHawk is calling the callback with an integer value in the val parameter, which is why the val.ball operation results in "attempt to index a number value". You could use a different name in one place or the other. Or you could omit the function parameters from the callback (since you aren't using them), so that values that are passed into the function are not bound to variable names.
Editor, Expert player (2548)
🇩🇪 Germany
Joined: 5/15/2007
Posts: 4120
Location: 🇩🇪 Germany
Is it possible to fetch the color hex value of a pixel on screen? If not, any chances of implementing such a lua function?
YoshiRulz
Any
Editor, Emulator Coder
Location: 🇦🇺 Sydney, Australia
Joined: 8/30/2020
Posts: 216
Location: 🇦🇺 Sydney, Australia
From Lua: No, you would have to save a screenshot and read it back from the disk. From C#: using BitmapBuffer bb = new(((IVideoProvider) vp).BufferWidth, vp.BufferHeight, vp.GetVideoBufferCopy()); bb.GetPixel(x, y)
I contribute to BizHawk as Linux/cross-platform lead, testing and automation lead, and UI designer. This year, I'm experimenting with streaming BizHawk development on Twitch. nope Links to find me elsewhere and to some of my side projects are on my personal site. I will respond on Discord faster than to PMs on this site.
Hey look buddy, I'm an engineer. That means I solve problems. Not problems like "What is software," because that would fall within the purview of your conundrums of philosophy. I solve practical problems. For instance, how am I gonna stop some high-wattage thread-ripping monster of a CPU dead in its tracks? The answer: use code. And if that don't work? Use more code.
Post subject: A nicer interface to SQLite from BizHawk
Sand
He/Him
Editor, Player (167)
Joined: 6/26/2018
Posts: 237
User movie #639221830998247534 is a Lua module for BizHawk that wraps the built-in SQL module to make it easier to use. In particular, it makes the structure of result sets more natural, it converts various conditions and errors that the SQL module signals with magic strings into regular Lua values and errors, and it makes it possible to open multiple database files (even from multiple scripts) at the same time.
Language: lua

local sqlite = require("sqlite") local db = sqlite.open("lag.db") local lag_frames = {} for _, row in ipairs(assert(db:execute("SELECT frame, islagged FROM lag"))) do if row.islagged ~= 0 then lag_frames[row.frame] = true end end
If you're like me, you were excited when you saw that BizHawk has a built-in built-in SQL module, because easy access to SQLite is very handy. But then you were disappointed when you tried to use it, because the interface is awkward and confusing. Here are some things that make it hard to use:
  • The SQL.opendatabase function doesn't return anything like a database handle. The SQL module internally supports only one database connection at a time, stored in a singleton variable called _dbConnection. When you call SQL.opendatabase, it forgets the previous database connection and replaces it with a new one. Calls to SQL.readcommand and SQL.writecommand use whatever database was most recently opened—and the effect is global, across all running scripts. If the script A.lua opens the database A.db, then script B.lua opens the database B.db, all of a sudden A.lua will unexpectedly interacting with B.db, most likely resulting in errors. Consider these two scripts, for example: Download A.lua
    Language: lua

    SQL.opendatabase("A.db") function assert_writecommand_ok(res) assert(res == "Command ran successfully", res) end assert_writecommand_ok(SQL.writecommand( [[CREATE TABLE IF NOT EXISTS lag ( frame INTEGER NOT NULL, islagged INTEGER NOT NULL ) STRICT]])) event.onframeend(function () assert_writecommand_ok(SQL.writecommand(string.format( "INSERT INTO lag VALUES (%d, %d)", emu.framecount(), ({[false] = 0, [true] = 1})[emu.islagged()] ))) end)
    Download B.lua
    Language: lua

    SQL.opendatabase("B.db") function assert_writecommand_ok(res) assert(res == "Command ran successfully", res) end assert_writecommand_ok(SQL.writecommand( [[CREATE TABLE IF NOT EXISTS inputpoll ( cycles INTEGER NOT NULL ) STRICT]])) event.oninputpoll(function () assert_writecommand_ok(SQL.writecommand(string.format( "INSERT INTO inputpoll VALUES (%d)", emu.totalexecutedcycles() ))) end)
    If you load these two scripts at the same time, one of them will crash with an error, depending on which was loaded first:
    error running function attached by the event OnInputPoll
    Error message: [string "main"]:2: SQLite Error 1: 'no such table: inputpoll'.
    
    NLua.Exceptions.LuaScriptException: [string "main"]:2: SQLite Error 1: 'no such table: lag'.
    
  • Most of the functions in the SQL module return magic strings to indicate success or error conditions, like: Proper use of the module requires checking for these strings and interpreting them appropriately.
  • The format in which results are returned is strange. Suppose you had this table:
    CREATE TABLE tbl (x INTEGER, y STRING);
    INSERT INTO tbl VALUES(123,'abc');
    INSERT INTO tbl VALUES(999,'hello');
    INSERT INTO tbl VALUES(456,NULL);
    INSERT INTO tbl VALUES(NULL,'xyz');
    
    xy
    123'abc'
    999'hello'
    456NULL
    NULL'xyz'
    If you run the query SELECT x, y FROM tbl, you might reasonably expect to get a Lua table like this, with one entry for each row, and each row being represented by a key–value table:
    {{x = 123, y = "abc"},
     {x = 999, y = "hello"},
     {x = 456},
     {y = "xyz"}}
    
    Instead, results are returned in single flat key–value table, where keys encode both a column name and a row number. NULL values are not represented by a Lua nil, but by a particular kind of userdata:
    {["x 0"] = 123, ["y 0"] = "abc",
     ["x 1"] = 999, ["y 1"] = "hello",
     ["x 2"] = 456, ["y 2"] = userdata,
     ["x 3"] = userdata, ["y 3"] = "xyz"}
    
The wrapper module provides the following advantages and conveniences over the built-in SQL module:
  • It simulates having handles to multiple databases open simultaneously. Calling sqlite.open gives you a handle that you can then use to execute statements on that database and no other. How this works is pretty simple. A database handle contains a filesystem path. Before executing a statement, the module re-opens the database at the given path so that the SQL module's global database connection points to the right place. That means you can run these two scripts at the same time, and they do not interfere with each other: Download A2.lua
    Language: lua

    local sqlite = require("sqlite") local db = sqlite.open("A2.db") assert(db:execute( [[CREATE TABLE IF NOT EXISTS lag ( frame INTEGER NOT NULL, islagged INTEGER NOT NULL ) STRICT]])) event.onframeend(function () assert(db:execute(string.format( "INSERT INTO lag VALUES (%s, %s)", sqlite.escape(emu.framecount(), emu.islagged()) ))) end)
    Download B2.lua
    Language: lua

    local sqlite = require("sqlite") local db = sqlite.open("B2.db") assert(db:execute( [[CREATE TABLE IF NOT EXISTS inputpoll ( cycles INTEGER NOT NULL ) STRICT]])) event.oninputpoll(function () assert(db:execute(string.format( "INSERT INTO inputpoll VALUES (%s)", sqlite.escape(emu.totalexecutedcycles()) ))) end)
    As an extra precaution, the module also restores the global database connection to what is was originally, after executing each statement. That means that scripts that use the wrapper module can run at the same time as up to one other script that uses the SQL module directly. (If two or more other scripts use the SQL module directly, they will still stomp on each other, and there's nothing we can do about that.)
  • It interprets the various magic strings that the SQL module returns and converts them to plain Lua values or error returns. A query that successfully returns 0 rows results in an empty table, not the string "No rows found".
  • It restructures result sets to use the more natural sequential row structure described above. You can iterate over the rows with ipairs and access the value of columns with normal table indexing.
Another way of working around the awkward interface of the SQL module is not to use it at all, and instead to install some third-party module such as LuaSQLite3. But this wrapper module can improve the experience of using SQLite from BizHawk without adding an external dependency.
1 2
14 15