Merge pull request #1642 from cracyc/luaconsole

Make console a Lua plugin
This commit is contained in:
cracyc 2016-11-06 18:57:19 -06:00 committed by GitHub
commit 846dfa8ffd
10 changed files with 201 additions and 190 deletions

View File

@ -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
View 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

View File

@ -0,0 +1,10 @@
{
"plugin": {
"name": "console",
"description": "Console plugin",
"version": "0.0.1",
"author": "Carl",
"type": "plugin",
"start": "false"
}
}

View File

@ -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",

View File

@ -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)

View File

@ -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();
}

View File

@ -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 */

View File

@ -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),

View File

@ -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__ */

View File

@ -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()