mirror of
https://github.com/holub/mame
synced 2025-04-22 16:31:49 +03:00
Merge pull request #1642 from cracyc/luaconsole
Make console a Lua plugin
This commit is contained in:
commit
846dfa8ffd
9
3rdparty/lua-linenoise/linenoise.c
vendored
9
3rdparty/lua-linenoise/linenoise.c
vendored
@ -101,6 +101,14 @@ static int l_historyadd(lua_State *L)
|
||||
return handle_ln_ok(L);
|
||||
}
|
||||
|
||||
static int l_preloadbuffer(lua_State *L)
|
||||
{
|
||||
const char *line = luaL_checkstring(L, 1);
|
||||
linenoisePreloadBuffer(line);
|
||||
|
||||
return handle_ln_ok(L);
|
||||
}
|
||||
|
||||
static int l_historysetmaxlen(lua_State *L)
|
||||
{
|
||||
int len = luaL_checkinteger(L, 1);
|
||||
@ -168,6 +176,7 @@ luaL_Reg linenoise_funcs[] = {
|
||||
{ "clearscreen", l_clearscreen },
|
||||
{ "setcompletion", l_setcompletion},
|
||||
{ "addcompletion", l_addcompletion },
|
||||
{ "preload", l_preloadbuffer },
|
||||
|
||||
/* Aliases for more consistent function names */
|
||||
{ "addhistory", l_historyadd },
|
||||
|
104
plugins/console/init.lua
Normal file
104
plugins/console/init.lua
Normal file
@ -0,0 +1,104 @@
|
||||
-- license:BSD-3-Clause
|
||||
-- copyright-holders:Carl
|
||||
local exports = {}
|
||||
exports.name = "console"
|
||||
exports.version = "0.0.1"
|
||||
exports.description = "Console plugin"
|
||||
exports.license = "The BSD 3-Clause License"
|
||||
exports.author = { name = "Carl" }
|
||||
|
||||
local console = exports
|
||||
|
||||
function console.startplugin()
|
||||
local conth = emu.thread()
|
||||
local started = false
|
||||
local ln = require("linenoise")
|
||||
local preload = false
|
||||
print(" _/ _/ _/_/ _/ _/ _/_/_/_/");
|
||||
print(" _/_/ _/_/ _/ _/ _/_/ _/_/ _/ ");
|
||||
print(" _/ _/ _/ _/_/_/_/ _/ _/ _/ _/_/_/ ");
|
||||
print(" _/ _/ _/ _/ _/ _/ _/ ");
|
||||
print("_/ _/ _/ _/ _/ _/ _/_/_/_/ \n");
|
||||
print(emu.app_name() .. " " .. emu.app_version(), "\nCopyright (C) Nicola Salmoria and the MAME team\n");
|
||||
print(_VERSION, "\nCopyright (C) Lua.org, PUC-Rio\n");
|
||||
-- linenoise isn't thread safe but that means history can handled here
|
||||
-- that also means that bad things will happen if anything outside lua tries to use it
|
||||
-- especially the completion callback
|
||||
ln.historysetmaxlen(10)
|
||||
local scr = "local ln = require('linenoise')\n"
|
||||
scr = scr .. "ln.setcompletion(function(c, str) status = str\n"
|
||||
scr = scr .. " yield()\n" -- coroutines can't yield in the middle of a callback so this is a real thread
|
||||
scr = scr .. " status:gsub('[^,]*', function(s) if s ~= '' then ln.addcompletion(c, s) end end)\n"
|
||||
scr = scr .. "end)\n"
|
||||
scr = scr .. "return ln.linenoise('\x1b[1;36m[MAME]\x1b[0m> ')"
|
||||
|
||||
local function get_completions(str)
|
||||
local function is_pair_iterable(t)
|
||||
local mt = getmetatable(t)
|
||||
return type(t) == 'table' or (mt and mt.__pairs)
|
||||
end
|
||||
local comps = ","
|
||||
local table = str:match("([(]?[%w.:()]-)[:.]?[%w_]*$")
|
||||
local rest, last = str:match("(.-[:.]?)([%w_]*)$")
|
||||
local err
|
||||
if table == "" then
|
||||
table = "_G"
|
||||
end
|
||||
err, tablef = pcall(load("return " .. table))
|
||||
if (not err) or (not tablef) then
|
||||
return comps
|
||||
end
|
||||
if is_pair_iterable(tablef) then
|
||||
for k, v in pairs(tablef) do
|
||||
if k:match("^" .. last) then
|
||||
comps = comps .. "," .. rest .. k
|
||||
end
|
||||
end
|
||||
end
|
||||
local tablef = getmetatable(tablef)
|
||||
if is_pair_iterable(tablef) then
|
||||
for k, v in pairs(tablef) do
|
||||
if k:match("^" .. last) then
|
||||
comps = comps .. "," .. rest .. k
|
||||
end
|
||||
end
|
||||
end
|
||||
return comps
|
||||
end
|
||||
|
||||
emu.register_periodic(function()
|
||||
if conth.yield then
|
||||
conth:continue(get_completions(conth.result))
|
||||
return
|
||||
elseif conth.busy then
|
||||
return
|
||||
elseif started then
|
||||
local cmd = conth.result
|
||||
local func, err = load(cmd)
|
||||
if not func then
|
||||
if err:match("<eof>") then
|
||||
print("incomplete command")
|
||||
ln.preload(cmd)
|
||||
preload = true
|
||||
else
|
||||
print("error: ", err)
|
||||
preload = false
|
||||
end
|
||||
else
|
||||
preload = false
|
||||
local status
|
||||
status, err = pcall(func)
|
||||
if not status then
|
||||
print("error: ", err)
|
||||
end
|
||||
end
|
||||
if not preload then
|
||||
ln.historyadd(cmd)
|
||||
end
|
||||
end
|
||||
conth:start(scr)
|
||||
started = true
|
||||
end)
|
||||
end
|
||||
|
||||
return exports
|
10
plugins/console/plugin.json
Normal file
10
plugins/console/plugin.json
Normal file
@ -0,0 +1,10 @@
|
||||
{
|
||||
"plugin": {
|
||||
"name": "console",
|
||||
"description": "Console plugin",
|
||||
"version": "0.0.1",
|
||||
"author": "Carl",
|
||||
"type": "plugin",
|
||||
"start": "false"
|
||||
}
|
||||
}
|
@ -66,8 +66,6 @@ files {
|
||||
MAME_DIR .. "src/frontend/mame/cheat.h",
|
||||
MAME_DIR .. "src/frontend/mame/clifront.cpp",
|
||||
MAME_DIR .. "src/frontend/mame/clifront.h",
|
||||
MAME_DIR .. "src/frontend/mame/console.cpp",
|
||||
MAME_DIR .. "src/frontend/mame/console.h",
|
||||
MAME_DIR .. "src/frontend/mame/info.cpp",
|
||||
MAME_DIR .. "src/frontend/mame/info.h",
|
||||
MAME_DIR .. "src/frontend/mame/language.cpp",
|
||||
|
@ -29,7 +29,6 @@
|
||||
#include "ui/moptions.h"
|
||||
#include "language.h"
|
||||
#include "pluginopts.h"
|
||||
#include "console.h"
|
||||
|
||||
#include <new>
|
||||
#include <ctype.h>
|
||||
@ -312,13 +311,7 @@ int cli_frontend::execute(int argc, char **argv)
|
||||
|
||||
manager->start_luaengine();
|
||||
|
||||
if (m_options.console()) {
|
||||
//manager->lua()->start_console();
|
||||
console_frontend console(m_options, m_osd);
|
||||
console.start_console();
|
||||
} else {
|
||||
start_execution(manager, argc, argv, option_errors);
|
||||
}
|
||||
}
|
||||
// handle exceptions of various types
|
||||
catch (emu_fatalerror &fatal)
|
||||
|
@ -1,138 +0,0 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Miodrag Milanovic
|
||||
/***************************************************************************
|
||||
|
||||
console.c
|
||||
|
||||
Console interface frontend for MAME.
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#include "emu.h"
|
||||
#include "luaengine.h"
|
||||
#include "console.h"
|
||||
#include "linenoise-ng/include/linenoise.h"
|
||||
#include "mame.h"
|
||||
|
||||
#include <atomic>
|
||||
#include <thread>
|
||||
|
||||
static console_frontend *gConsole = nullptr;
|
||||
//-------------------------------------------------
|
||||
// console_frontend - constructor
|
||||
//-------------------------------------------------
|
||||
|
||||
console_frontend::console_frontend(emu_options &options, osd_interface &osd)
|
||||
: //m_options(options),
|
||||
//m_osd(osd),
|
||||
m_run(true),
|
||||
m_wait(false),
|
||||
m_prompt("\x1b[1;36m[MAME]\x1b[0m> ")
|
||||
{
|
||||
mame_machine_manager::instance()->lua()->sol()["quit"] = [this]() { cmd_quit(); };
|
||||
m_commands.push_back("quit()");
|
||||
mame_machine_manager::instance()->lua()->sol()["exit"] = [this]() { cmd_quit(); };
|
||||
m_commands.push_back("exit()");
|
||||
gConsole = this;
|
||||
}
|
||||
|
||||
|
||||
//-------------------------------------------------
|
||||
// ~console_frontend - destructor
|
||||
//-------------------------------------------------
|
||||
|
||||
console_frontend::~console_frontend()
|
||||
{
|
||||
}
|
||||
|
||||
void console_frontend::completion(char const* prefix, linenoiseCompletions* lc)
|
||||
{
|
||||
for (auto cmd : m_commands)
|
||||
{
|
||||
if (strncmp(prefix, cmd.c_str(), strlen(prefix)) == 0)
|
||||
{
|
||||
linenoiseAddCompletion(lc, cmd.c_str());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void console_frontend::cmd_quit()
|
||||
{
|
||||
printf("Exiting application\n");
|
||||
m_run.store(false);
|
||||
m_wait.store(false);
|
||||
}
|
||||
|
||||
void console_frontend::read_console(std::string &cmdLine)
|
||||
{
|
||||
while (m_run.load())
|
||||
{
|
||||
while (m_wait.load())
|
||||
{
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
if (!m_run.load()) break;
|
||||
char* result = linenoise(m_prompt.c_str());
|
||||
if (result == NULL)
|
||||
{
|
||||
continue;
|
||||
}
|
||||
else if (*result == '\0') {
|
||||
free(result);
|
||||
continue;
|
||||
}
|
||||
cmdLine = std::string(result);
|
||||
linenoiseHistoryAdd(result);
|
||||
//m_prompt = "\x1b[1;36m[MAME]\x1b[0m \x1b[1;32m[test]\x1b[0m> ";
|
||||
|
||||
free(result);
|
||||
m_wait.store(true);
|
||||
}
|
||||
}
|
||||
|
||||
static void completionHook(char const* prefix, linenoiseCompletions* lc)
|
||||
{
|
||||
gConsole->completion(prefix, lc);
|
||||
}
|
||||
|
||||
void console_frontend::start_console()
|
||||
{
|
||||
linenoiseInstallWindowChangeHandler();
|
||||
std::string cmdLine = "";
|
||||
const char* file = "./history";
|
||||
|
||||
linenoiseHistoryLoad(file);
|
||||
linenoiseSetCompletionCallback(completionHook);
|
||||
|
||||
// Display app info
|
||||
printf(" _/ _/ _/_/ _/ _/ _/_/_/_/\n");
|
||||
printf(" _/_/ _/_/ _/ _/ _/_/ _/_/ _/ \n");
|
||||
printf(" _/ _/ _/ _/_/_/_/ _/ _/ _/ _/_/_/ \n");
|
||||
printf(" _/ _/ _/ _/ _/ _/ _/ \n");
|
||||
printf("_/ _/ _/ _/ _/ _/ _/_/_/_/ \n");
|
||||
printf("\n");
|
||||
printf("%s v%s\n%s\n\n", emulator_info::get_appname(), build_version, emulator_info::get_copyright_info());
|
||||
|
||||
|
||||
std::thread cinThread(&console_frontend::read_console, this, std::ref(cmdLine));
|
||||
|
||||
while (m_run.load())
|
||||
{
|
||||
if (m_wait.load())
|
||||
{
|
||||
mame_machine_manager::instance()->lua()->load_string(cmdLine.c_str());
|
||||
cmdLine.clear();
|
||||
m_wait.store(false);
|
||||
} else {
|
||||
using namespace std::chrono_literals;
|
||||
std::this_thread::sleep_for(100ms);
|
||||
}
|
||||
}
|
||||
|
||||
m_run.store(false);
|
||||
cinThread.join();
|
||||
|
||||
linenoiseHistorySave(file);
|
||||
linenoiseHistoryFree();
|
||||
}
|
@ -1,41 +0,0 @@
|
||||
// license:BSD-3-Clause
|
||||
// copyright-holders:Miodrag Milanovic
|
||||
/***************************************************************************
|
||||
|
||||
console.c
|
||||
|
||||
Console interface frontend for MAME.
|
||||
|
||||
***************************************************************************/
|
||||
#ifndef MAME_FRONTEND_CONSOLE_H
|
||||
#define MAME_FRONTEND_CONSOLE_H
|
||||
|
||||
#pragma once
|
||||
|
||||
#include "emu.h"
|
||||
|
||||
struct linenoiseCompletions;
|
||||
|
||||
class console_frontend
|
||||
{
|
||||
public:
|
||||
// construction/destruction
|
||||
console_frontend(emu_options &options, osd_interface &osd);
|
||||
~console_frontend();
|
||||
|
||||
void start_console();
|
||||
void completion(char const* prefix, linenoiseCompletions* lc);
|
||||
|
||||
private:
|
||||
void read_console(std::string &cmdLine);
|
||||
|
||||
void cmd_quit();
|
||||
// internal state
|
||||
std::atomic<bool> m_run;
|
||||
std::atomic<bool> m_wait;
|
||||
std::string m_prompt;
|
||||
|
||||
std::vector<std::string> m_commands;
|
||||
};
|
||||
|
||||
#endif /* MAME_FRONTEND_CONSOLE_H */
|
@ -8,6 +8,7 @@
|
||||
|
||||
***************************************************************************/
|
||||
|
||||
#include <thread>
|
||||
#include <lua.hpp>
|
||||
#include "emu.h"
|
||||
#include "mame.h"
|
||||
@ -668,6 +669,11 @@ void lua_engine::on_frame_done()
|
||||
execute_function("LUA_ON_FRAME_DONE");
|
||||
}
|
||||
|
||||
void lua_engine::on_periodic()
|
||||
{
|
||||
execute_function("LUA_ON_PERIODIC");
|
||||
}
|
||||
|
||||
void lua_engine::attach_notifiers()
|
||||
{
|
||||
machine().add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&lua_engine::on_machine_prestart, this), true);
|
||||
@ -702,6 +708,7 @@ void lua_engine::initialize()
|
||||
* emu.register_resume(callback) - callback at resume
|
||||
* emu.register_frame(callback) - callback at end of frame
|
||||
* emu.register_frame_done(callback) - callback after frame is drawn to screen (for overlays)
|
||||
* emu.register_periodic(callback) - periodic callback while program is running
|
||||
* emu.register_menu(event_callback, populate_callback, name) - callbacks for plugin menu
|
||||
* emu.print_verbose(str) -- output to stderr at verbose level
|
||||
* emu.print_error(str) -- output to stderr at error level
|
||||
@ -735,6 +742,7 @@ void lua_engine::initialize()
|
||||
emu["register_resume"] = [this](sol::function func){ register_function(func, "LUA_ON_RESUME"); };
|
||||
emu["register_frame"] = [this](sol::function func){ register_function(func, "LUA_ON_FRAME"); };
|
||||
emu["register_frame_done"] = [this](sol::function func){ register_function(func, "LUA_ON_FRAME_DONE"); };
|
||||
emu["register_periodic"] = [this](sol::function func){ register_function(func, "LUA_ON_PERIODIC"); };
|
||||
emu["register_menu"] = [this](sol::function cb, sol::function pop, const std::string &name) {
|
||||
std::string cbfield = "menu_cb_" + name;
|
||||
std::string popfield = "menu_pop_" + name;
|
||||
@ -767,6 +775,7 @@ void lua_engine::initialize()
|
||||
}
|
||||
return item;
|
||||
};
|
||||
emu["thread"] = []() { context *ctx = new context; ctx->busy = false; ctx->yield = false; return ctx; };
|
||||
|
||||
emu.new_usertype<emu_file>("file", sol::call_constructor, sol::constructors<sol::types<const char *, uint32_t>>(),
|
||||
"read", [](emu_file &file, sol::buffer *buff) { buff->set_len(file.read(buff->get_ptr(), buff->get_len())); return buff; },
|
||||
@ -777,6 +786,64 @@ void lua_engine::initialize()
|
||||
"filename", &emu_file::filename,
|
||||
"fullpath", &emu_file::fullpath);
|
||||
|
||||
/*
|
||||
* thread.start(scr) - run scr (string not function) in a seperate thread in a new empty (other then modules) lua context
|
||||
* thread.continue(val) - resume thread and pass val to it
|
||||
* thread.result() - get thread result as string
|
||||
* thread.busy - check if thread is running
|
||||
* thread.yield - check if thread is yielded
|
||||
*/
|
||||
|
||||
sol().new_usertype<context>("thread",
|
||||
sol::meta_function::garbage_collect, sol::destructor([](context *ctx) { delete ctx; }),
|
||||
"start", [this](context &ctx, const char *scr) {
|
||||
std::string script(scr);
|
||||
if(ctx.busy)
|
||||
return false;
|
||||
std::thread th([&ctx, script]() {
|
||||
sol::state thstate;
|
||||
thstate.open_libraries();
|
||||
thstate["package"]["preload"]["zlib"] = &luaopen_zlib;
|
||||
thstate["package"]["preload"]["lfs"] = &luaopen_lfs;
|
||||
thstate["package"]["preload"]["linenoise"] = &luaopen_linenoise;
|
||||
sol::load_result res = thstate.load(script);
|
||||
if(res.valid())
|
||||
{
|
||||
sol::protected_function func = res.get<sol::protected_function>();
|
||||
thstate["yield"] = [&ctx, &thstate]() {
|
||||
std::mutex m;
|
||||
std::unique_lock<std::mutex> lock(m);
|
||||
ctx.result = thstate["status"];
|
||||
ctx.yield = true;
|
||||
ctx.sync.wait(lock);
|
||||
ctx.yield = false;
|
||||
thstate["status"] = ctx.result;
|
||||
};
|
||||
auto ret = func();
|
||||
if(ret.valid())
|
||||
ctx.result = ret.get<const char *>();
|
||||
}
|
||||
ctx.busy = false;
|
||||
});
|
||||
ctx.busy = true;
|
||||
ctx.yield = false;
|
||||
th.detach();
|
||||
return true;
|
||||
},
|
||||
"continue", [this](context &ctx, const char *val) {
|
||||
if(!ctx.yield)
|
||||
return;
|
||||
ctx.result = val;
|
||||
ctx.sync.notify_all();
|
||||
},
|
||||
"result", sol::property([this](context &ctx) -> std::string {
|
||||
if(ctx.busy && !ctx.yield)
|
||||
return "";
|
||||
return ctx.result;
|
||||
}),
|
||||
"busy", sol::readonly(&context::busy),
|
||||
"yield", sol::readonly(&context::yield));
|
||||
|
||||
sol().new_usertype<save_item>("item",
|
||||
sol::meta_function::garbage_collect, sol::destructor([](save_item *item) { delete item; }),
|
||||
"size", sol::readonly(&save_item::size),
|
||||
|
@ -18,12 +18,11 @@
|
||||
#define __LUA_ENGINE_H__
|
||||
|
||||
#include <map>
|
||||
#include <condition_variable>
|
||||
#define SOL_SAFE_USERTYPE
|
||||
//#define SOL_CHECK_ARGUMENTS
|
||||
#include "sol2/sol.hpp"
|
||||
|
||||
class cheat_manager;
|
||||
|
||||
struct lua_State;
|
||||
|
||||
class lua_engine
|
||||
@ -46,6 +45,7 @@ public:
|
||||
std::vector<std::string> &get_menu() { return m_menu; }
|
||||
void attach_notifiers();
|
||||
void on_frame_done();
|
||||
void on_periodic();
|
||||
|
||||
template<typename T, typename U>
|
||||
bool call_plugin(const std::string &name, const T in, U &out)
|
||||
@ -153,6 +153,14 @@ private:
|
||||
void close();
|
||||
|
||||
void run(sol::load_result res);
|
||||
|
||||
struct context
|
||||
{
|
||||
std::string result;
|
||||
std::condition_variable sync;
|
||||
bool busy;
|
||||
bool yield;
|
||||
};
|
||||
};
|
||||
|
||||
#endif /* __LUA_ENGINE_H__ */
|
||||
|
@ -326,6 +326,7 @@ void emulator_info::draw_user_interface(running_machine& machine)
|
||||
|
||||
void emulator_info::periodic_check()
|
||||
{
|
||||
return mame_machine_manager::instance()->lua()->on_periodic();
|
||||
}
|
||||
|
||||
bool emulator_info::frame_hook()
|
||||
|
Loading…
Reference in New Issue
Block a user