diff options
Diffstat (limited to 'src/frontend/mame/luaengine.cpp')
-rw-r--r-- | src/frontend/mame/luaengine.cpp | 1194 |
1 files changed, 744 insertions, 450 deletions
diff --git a/src/frontend/mame/luaengine.cpp b/src/frontend/mame/luaengine.cpp index 2cc98b971fe..6aa2d3840ec 100644 --- a/src/frontend/mame/luaengine.cpp +++ b/src/frontend/mame/luaengine.cpp @@ -21,14 +21,19 @@ #include "debugger.h" #include "drivenum.h" #include "emuopts.h" +#include "fileio.h" #include "inputdev.h" #include "natkeyboard.h" +#include "screen.h" #include "softlist.h" #include "uiinput.h" #include "corestr.h" +#include <algorithm> +#include <condition_variable> #include <cstring> +#include <mutex> #include <thread> @@ -36,15 +41,11 @@ // LUA ENGINE //************************************************************************** -extern "C" { - -int luaopen_zlib(lua_State *L); -int luaopen_lfs(lua_State *L); +int luaopen_zlib(lua_State *const L); +extern "C" int luaopen_lfs(lua_State *L); int luaopen_linenoise(lua_State *L); int luaopen_lsqlite3(lua_State *L); -} // extern "C" - template <typename T> struct lua_engine::devenum @@ -59,6 +60,128 @@ struct lua_engine::devenum namespace { +struct save_item +{ + void *base; + unsigned int size; + unsigned int count; + unsigned int valcount; + unsigned int blockcount; + unsigned int stride; +}; + +struct thread_context +{ +private: + sol::state m_state; + std::string m_result; + std::mutex m_guard; + std::condition_variable m_sync; + bool m_busy = false; + +public: + bool m_yield = false; + + thread_context() + { + m_state.open_libraries(); + m_state["package"]["preload"]["zlib"] = &luaopen_zlib; + m_state["package"]["preload"]["lfs"] = &luaopen_lfs; + m_state["package"]["preload"]["linenoise"] = &luaopen_linenoise; + m_state.set_function("yield", + [this] () + { + std::unique_lock<std::mutex> yield_lock(m_guard); + m_result = m_state["status"]; + m_yield = true; + m_sync.wait(yield_lock); + m_yield = false; + }); + } + + bool start(sol::this_state s, char const *scr) + { + std::unique_lock<std::mutex> caller_lock(m_guard); + if (m_busy) + return false; + + sol::load_result res = m_state.load(scr); + if (!res.valid()) + { + sol::error err = res; + luaL_error(s, err.what()); + return false; // unreachable - luaL_error throws + } + + std::thread th( + [this, func = res.get<sol::protected_function>()] () + { + auto ret = func(); + std::unique_lock<std::mutex> result_lock(m_guard); + if (ret.valid()) + { + auto result = ret.get<std::optional<char const *> >(); + if (!result) + osd_printf_error("[LUA ERROR] in thread: return value must be string\n"); + else if (!*result) + m_result.clear(); + else + m_result = *result; + } + else + { + sol::error err = ret; + osd_printf_error("[LUA ERROR] in thread: %s\n", err.what()); + } + m_busy = false; + }); + m_busy = true; + m_yield = false; + th.detach(); // FIXME: this is unsafe as the thread function modifies members of the object + return true; + } + + void resume(char const *val) + { + std::unique_lock<std::mutex> lock(m_guard); + if (m_yield) + { + if (val) + m_state["status"] = val; + else + m_state["status"] = sol::lua_nil; + m_sync.notify_all(); + } + } + + char const *result() + { + std::unique_lock<std::mutex> lock(m_guard); + if (m_busy && !m_yield) + return ""; + else + return m_result.c_str(); + } + + bool busy() const + { + return m_busy; + } +}; + + +struct device_state_entries +{ + device_state_entries(device_state_interface const &s) : state(s) { } + device_state_interface::entrylist_type const &items() { return state.state_entries(); } + + static device_state_entry const &unwrap(device_state_interface::entrylist_type::const_iterator const &it) { return **it; } + static int push_key(lua_State *L, device_state_interface::entrylist_type::const_iterator const &it, std::size_t ix) { return sol::stack::push_reference(L, (*it)->symbol()); } + + device_state_interface const &state; +}; + + struct image_interface_formats { image_interface_formats(device_image_interface &i) : image(i) { } @@ -82,26 +205,39 @@ struct plugin_options_plugins plugin_options &options; }; -} // anonymous namespace - -namespace sol +template <typename T> +void resume_tasks(lua_State *L, T &&tasks, bool status) { + for (int ref : tasks) + { + lua_rawgeti(L, LUA_REGISTRYINDEX, ref); + lua_State *const thread = lua_tothread(L, -1); + lua_pop(L, 1); + lua_pushboolean(thread, status ? 1 : 0); + int nresults = 0; + int const stat = lua_resume(thread, nullptr, 1, &nresults); + if ((stat != LUA_OK) && (stat != LUA_YIELD)) + { + osd_printf_error("[LUA ERROR] in resume: %s\n", lua_tostring(thread, -1)); + lua_pop(thread, 1); + } + else + { + lua_pop(thread, nresults); + } + luaL_unref(L, LUA_REGISTRYINDEX, ref); + } +} -template <> struct is_container<image_interface_formats> : std::true_type { }; -template <> struct is_container<plugin_options_plugins> : std::true_type { }; +} // anonymous namespace -sol::buffer *sol_lua_get(sol::types<buffer *>, lua_State *L, int index, sol::stack::record &tracking) -{ - return new sol::buffer(stack::get<int>(L, index), L); -} +namespace sol { -int sol_lua_push(sol::types<buffer *>, lua_State *L, buffer *value) -{ - delete value; - return 1; -} +template <> struct is_container<device_state_entries> : std::true_type { }; +template <> struct is_container<image_interface_formats> : std::true_type { }; +template <> struct is_container<plugin_options_plugins> : std::true_type { }; template <typename T> @@ -208,6 +344,34 @@ public: template <> +struct usertype_container<device_state_entries> : lua_engine::immutable_sequence_helper<device_state_entries, device_state_interface::entrylist_type const, device_state_interface::entrylist_type::const_iterator> +{ +private: + using entrylist_type = device_state_interface::entrylist_type; + +public: + static int get(lua_State *L) + { + device_state_entries &self(get_self(L)); + char const *const symbol(stack::unqualified_get<char const *>(L)); + auto const found(std::find_if( + self.state.state_entries().begin(), + self.state.state_entries().end(), + [&symbol] (std::unique_ptr<device_state_entry> const &v) { return !std::strcmp(v->symbol(), symbol); })); + if (self.state.state_entries().end() != found) + return stack::push_reference(L, std::cref(**found)); + else + return stack::push(L, lua_nil); + } + + static int index_get(lua_State *L) + { + return get(L); + } +}; + + +template <> struct usertype_container<image_interface_formats> : lua_engine::immutable_sequence_helper<image_interface_formats, device_image_interface::formatlist_type const, device_image_interface::formatlist_type::const_iterator> { private: @@ -223,7 +387,7 @@ public: self.image.formatlist().end(), [&name] (std::unique_ptr<image_device_format> const &v) { return v->name() == name; })); if (self.image.formatlist().end() != found) - return stack::push_reference(L, **found); + return stack::push_reference(L, std::cref(**found)); else return stack::push(L, lua_nil); } @@ -251,7 +415,7 @@ public: self.options.plugins().end(), [&name] (plugin_options::plugin const &p) { return p.m_name == name; })); if (self.options.plugins().end() != found) - return stack::push_reference(L, *found); + return stack::push_reference(L, std::cref(*found)); else return stack::push(L, lua_nil); } @@ -287,26 +451,6 @@ int sol_lua_push(sol::types<screen_type_enum>, lua_State *L, screen_type_enum && return sol::stack::push(L, "unknown"); } -int sol_lua_push(sol::types<image_init_result>, lua_State *L, image_init_result &&value) -{ - switch (value) - { - case image_init_result::PASS: return sol::stack::push(L, "pass"); - case image_init_result::FAIL: return sol::stack::push(L, "fail"); - } - return sol::stack::push(L, "invalid"); -} - -int sol_lua_push(sol::types<image_verify_result>, lua_State *L, image_verify_result &&value) -{ - switch (value) - { - case image_verify_result::PASS: return sol::stack::push(L, "pass"); - case image_verify_result::FAIL: return sol::stack::push(L, "fail"); - } - return sol::stack::push(L, "invalid"); -} - //------------------------------------------------- // process_snapshot_filename - processes a snapshot @@ -330,13 +474,16 @@ static std::string process_snapshot_filename(running_machine &machine, const cha //------------------------------------------------- lua_engine::lua_engine() + : m_lua_state(nullptr) + , m_machine(nullptr) + , m_timer(nullptr) { - m_machine = nullptr; - m_lua_state = luaL_newstate(); /* create state */ + m_lua_state = luaL_newstate(); // create state m_sol_state = std::make_unique<sol::state_view>(m_lua_state); // create sol view + m_notifiers.emplace(); luaL_checkversion(m_lua_state); - lua_gc(m_lua_state, LUA_GCSTOP, 0); /* stop collector during initialization */ + lua_gc(m_lua_state, LUA_GCSTOP, 0); // stop collector during initialization sol().open_libraries(); // Get package.preload so we can store builtins in it. @@ -361,16 +508,18 @@ sol::object lua_engine::call_plugin(const std::string &name, sol::object in) { std::string field = "cb_" + name; sol::object obj = sol().registry()[field]; - if(obj.is<sol::protected_function>()) + if (obj.is<sol::protected_function>()) { auto res = invoke(obj.as<sol::protected_function>(), in); - if(!res.valid()) + if (!res.valid()) { sol::error err = res; osd_printf_error("[LUA ERROR] in call_plugin: %s\n", err.what()); } else + { return res.get<sol::object>(); + } } return sol::lua_nil; } @@ -389,7 +538,7 @@ std::optional<long> lua_engine::menu_populate(const std::string &menu, std::vect } else { - std::tuple<sol::table, std::optional<long>, std::string> table = res; + std::tuple<sol::table, std::optional<long>, std::optional<std::string> > table = res; for (auto &entry : std::get<0>(table)) { if (entry.second.is<sol::table>()) @@ -398,7 +547,10 @@ std::optional<long> lua_engine::menu_populate(const std::string &menu, std::vect menu_list.emplace_back(enttable.get<std::string, std::string, std::string>(1, 2, 3)); } } - flags = std::get<2>(table); + if (std::get<2>(table)) + flags = *std::get<2>(table); + else + flags.clear(); return std::get<1>(table); } } @@ -409,7 +561,7 @@ std::optional<long> lua_engine::menu_populate(const std::string &menu, std::vect std::pair<bool, std::optional<long> > lua_engine::menu_callback(const std::string &menu, int index, const std::string &event) { std::string field = "menu_cb_" + menu; - std::pair<bool, std::optional<long> > ret(false, std::nullopt); + std::pair<std::optional<bool>, std::optional<long> > ret(false, std::nullopt); sol::object obj = sol().registry()[field]; if (obj.is<sol::protected_function>()) { @@ -424,7 +576,7 @@ std::pair<bool, std::optional<long> > lua_engine::menu_callback(const std::strin ret = res; } } - return ret; + return std::make_pair(std::get<0>(ret) && *std::get<0>(ret), std::get<1>(ret)); } void lua_engine::set_machine(running_machine *machine) @@ -432,9 +584,10 @@ void lua_engine::set_machine(running_machine *machine) m_machine = machine; } -int lua_engine::enumerate_functions(const char *id, std::function<bool(const sol::protected_function &func)> &&callback) +template <typename T> +size_t lua_engine::enumerate_functions(const char *id, T &&callback) { - int count = 0; + size_t count = 0; sol::object functable = sol().registry()[id]; if (functable.is<sol::table>()) { @@ -448,23 +601,24 @@ int lua_engine::enumerate_functions(const char *id, std::function<bool(const sol break; } } - return true; } return count; } bool lua_engine::execute_function(const char *id) { - int count = enumerate_functions(id, [this](const sol::protected_function &func) - { - auto ret = invoke(func); - if(!ret.valid()) - { - sol::error err = ret; - osd_printf_error("[LUA ERROR] in execute_function: %s\n", err.what()); - } - return true; - }); + size_t count = enumerate_functions( + id, + [this] (const sol::protected_function &func) + { + auto ret = invoke(func); + if (!ret.valid()) + { + sol::error err = ret; + osd_printf_error("[LUA ERROR] in execute_function: %s\n", err.what()); + } + return true; + }); return count > 0; } @@ -482,13 +636,25 @@ void lua_engine::on_machine_prestart() execute_function("LUA_ON_PRESTART"); } -void lua_engine::on_machine_start() +void lua_engine::on_machine_reset() { + m_notifiers->on_reset(); execute_function("LUA_ON_START"); } void lua_engine::on_machine_stop() { + // clear waiting tasks + m_timer = nullptr; + std::vector<int> expired; + expired.reserve(m_waiting_tasks.size()); + for (auto const &waiting : m_waiting_tasks) + expired.emplace_back(waiting.second); + m_waiting_tasks.clear(); + resume_tasks(m_lua_state, expired, false); + expired.clear(); + + m_notifiers->on_stop(); execute_function("LUA_ON_STOP"); } @@ -499,22 +665,45 @@ void lua_engine::on_machine_before_load_settings() void lua_engine::on_machine_pause() { + m_notifiers->on_pause(); execute_function("LUA_ON_PAUSE"); } void lua_engine::on_machine_resume() { + m_notifiers->on_resume(); execute_function("LUA_ON_RESUME"); } void lua_engine::on_machine_frame() { + std::vector<int> tasks = std::move(m_frame_tasks); + m_frame_tasks.clear(); + resume_tasks(m_lua_state, tasks, true); // TODO: doesn't need to return anything + + m_notifiers->on_frame(); + execute_function("LUA_ON_FRAME"); } -void lua_engine::on_frame_done() +void lua_engine::on_machine_presave() +{ + m_notifiers->on_presave(); +} + +void lua_engine::on_machine_postload() { - execute_function("LUA_ON_FRAME_DONE"); + // clear waiting tasks + m_timer->reset(); + std::vector<int> expired; + expired.reserve(m_waiting_tasks.size()); + for (auto const &waiting : m_waiting_tasks) + expired.emplace_back(waiting.second); + m_waiting_tasks.clear(); + resume_tasks(m_lua_state, expired, false); + expired.clear(); + + m_notifiers->on_postload(); } void lua_engine::on_sound_update() @@ -530,32 +719,38 @@ void lua_engine::on_periodic() bool lua_engine::on_missing_mandatory_image(const std::string &instance_name) { bool handled = false; - enumerate_functions("LUA_ON_MANDATORY_FILE_MANAGER_OVERRIDE", [this, &instance_name, &handled](const sol::protected_function &func) - { - auto ret = invoke(func, instance_name); + enumerate_functions( + "LUA_ON_MANDATORY_FILE_MANAGER_OVERRIDE", + [this, &instance_name, &handled] (const sol::protected_function &func) + { + auto ret = invoke(func, instance_name); - if(!ret.valid()) - { - sol::error err = ret; - osd_printf_error("[LUA ERROR] in on_missing_mandatory_image: %s\n", err.what()); - } - else if (ret.get<bool>()) - { - handled = true; - } - return !handled; - }); + if (!ret.valid()) + { + sol::error err = ret; + osd_printf_error("[LUA ERROR] in on_missing_mandatory_image: %s\n", err.what()); + } + else if (ret.get<bool>()) + { + handled = true; + } + return !handled; + }); return handled; } void lua_engine::attach_notifiers() { machine().add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&lua_engine::on_machine_prestart, this), true); - machine().add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&lua_engine::on_machine_start, this)); + machine().add_notifier(MACHINE_NOTIFY_RESET, machine_notify_delegate(&lua_engine::on_machine_reset, this)); machine().add_notifier(MACHINE_NOTIFY_EXIT, machine_notify_delegate(&lua_engine::on_machine_stop, this)); machine().add_notifier(MACHINE_NOTIFY_PAUSE, machine_notify_delegate(&lua_engine::on_machine_pause, this)); machine().add_notifier(MACHINE_NOTIFY_RESUME, machine_notify_delegate(&lua_engine::on_machine_resume, this)); machine().add_notifier(MACHINE_NOTIFY_FRAME, machine_notify_delegate(&lua_engine::on_machine_frame, this)); + machine().save().register_presave(save_prepost_delegate(FUNC(lua_engine::on_machine_presave), this)); + machine().save().register_postload(save_prepost_delegate(FUNC(lua_engine::on_machine_postload), this)); + + m_timer = machine().scheduler().timer_alloc(timer_expired_delegate(FUNC(lua_engine::resume), this)); } //------------------------------------------------- @@ -596,9 +791,6 @@ void lua_engine::initialize() * emu.unpause() - unpause emulation * emu.step() - advance one frame * emu.keypost(keys) - post keys to natural keyboard - * emu.wait(len) - wait for len within coroutine - * emu.lang_translate(str) - get translation for str if available - * emu.subst_env(str) - substitute environment variables with values for str (semantics are OS-specific) * * emu.register_prestart(callback) - register callback before reset * emu.register_start(callback) - register callback after reset @@ -615,11 +807,6 @@ void lua_engine::initialize() * emu.register_before_load_settings(callback) - register callback to be run before settings are loaded * emu.show_menu(menu_name) - show menu by name and pause the machine * - * emu.print_verbose(str) - output to stderr at verbose level - * emu.print_error(str) - output to stderr at error level - * emu.print_info(str) - output to stderr at info level - * emu.print_debug(str) - output to stderr at debug level - * * emu.device_enumerator(dev) - get device enumerator starting at arbitrary point in tree * emu.screen_enumerator(dev) - get screen device enumerator starting at arbitrary point in tree * emu.image_enumerator(dev) - get image interface enumerator starting at arbitrary point in tree @@ -627,6 +814,79 @@ void lua_engine::initialize() */ sol::table emu = sol().create_named_table("emu"); + emu["wait"] = sol::yielding( + [this] (sol::this_state s, sol::object duration, sol::variadic_args args) + { + attotime delay; + if (!duration) + { + luaL_error(s, "waiting duration expected"); + } + else if (duration.is<attotime>()) + { + delay = duration.as<attotime>(); + } + else + { + auto seconds = duration.as<std::optional<double> >(); + if (!seconds) + luaL_error(s, "waiting duration must be attotime or number"); + delay = attotime::from_double(*seconds); + } + attotime const expiry = machine().time() + delay; + + int const ret = lua_pushthread(s); + if (ret == 1) + luaL_error(s, "cannot wait from outside coroutine"); + int const ref = luaL_ref(s, LUA_REGISTRYINDEX); + + auto const pos = std::upper_bound( + m_waiting_tasks.begin(), + m_waiting_tasks.end(), + expiry, + [] (auto const &a, auto const &b) { return a < b.first; }); + if (m_waiting_tasks.begin() == pos) + m_timer->reset(delay); + m_waiting_tasks.emplace(pos, expiry, ref); + + return sol::variadic_results(args.begin(), args.end()); + }); + emu["wait_next_update"] = sol::yielding( + [this] (sol::this_state s, sol::variadic_args args) + { + int const ret = lua_pushthread(s); + if (ret == 1) + luaL_error(s, "cannot wait from outside coroutine"); + m_update_tasks.emplace_back(luaL_ref(s, LUA_REGISTRYINDEX)); + return sol::variadic_results(args.begin(), args.end()); + }); + emu["wait_next_frame"] = sol::yielding( + [this] (sol::this_state s, sol::variadic_args args) + { + int const ret = lua_pushthread(s); + if (ret == 1) + luaL_error(s, "cannot wait from outside coroutine"); + m_frame_tasks.emplace_back(luaL_ref(s, LUA_REGISTRYINDEX)); + return sol::variadic_results(args.begin(), args.end()); + }); + emu.set_function("add_machine_reset_notifier", make_notifier_adder(m_notifiers->on_reset, "machine reset")); + emu.set_function("add_machine_stop_notifier", make_notifier_adder(m_notifiers->on_stop, "machine stop")); + emu.set_function("add_machine_pause_notifier", make_notifier_adder(m_notifiers->on_pause, "machine pause")); + emu.set_function("add_machine_resume_notifier", make_notifier_adder(m_notifiers->on_resume, "machine resume")); + emu.set_function("add_machine_frame_notifier", make_notifier_adder(m_notifiers->on_frame, "machine frame")); + emu.set_function("add_machine_pre_save_notifier", make_notifier_adder(m_notifiers->on_presave, "machine pre-save")); + emu.set_function("add_machine_post_load_notifier", make_notifier_adder(m_notifiers->on_postload, "machine post-load")); + emu.set_function("print_error", [] (const char *str) { osd_printf_error("%s\n", str); }); + emu.set_function("print_warning", [] (const char *str) { osd_printf_warning("%s\n", str); }); + emu.set_function("print_info", [] (const char *str) { osd_printf_info("%s\n", str); }); + emu.set_function("print_verbose", [] (const char *str) { osd_printf_verbose("%s\n", str); }); + emu.set_function("print_debug", [] (const char *str) { osd_printf_debug("%s\n", str); }); + emu["lang_translate"] = sol::overload( + static_cast<char const *(*)(char const *)>(&lang_translate), + static_cast<char const *(*)(char const *, char const *)>(&lang_translate)); + emu.set_function("subst_env", &osd_subst_env); + + // TODO: stuff below here needs to be rationalised emu["app_name"] = &emulator_info::get_appname_lower; emu["app_version"] = &emulator_info::get_bare_build_version; emu["gamename"] = [this] () { return machine().system().type.fullname(); }; @@ -654,11 +914,11 @@ void lua_engine::initialize() machine().resume(); }; emu["register_prestart"] = [this] (sol::function func) { register_function(func, "LUA_ON_PRESTART"); }; - emu["register_start"] = [this] (sol::function func) { register_function(func, "LUA_ON_START"); }; - emu["register_stop"] = [this] (sol::function func) { register_function(func, "LUA_ON_STOP"); }; - emu["register_pause"] = [this] (sol::function func) { register_function(func, "LUA_ON_PAUSE"); }; - 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_start"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_start is deprecated - please use emu.add_machine_reset_notifier\n"); register_function(func, "LUA_ON_START"); }; + emu["register_stop"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_stop is deprecated - please use emu.add_machine_stop_notifier\n"); register_function(func, "LUA_ON_STOP"); }; + emu["register_pause"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_pause is deprecated - please use emu.add_machine_pause_notifier\n"); register_function(func, "LUA_ON_PAUSE"); }; + emu["register_resume"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_resume is deprecated - please use emu.add_machine_resume_notifier\n"); register_function(func, "LUA_ON_RESUME"); }; + emu["register_frame"] = [this] (sol::function func) { osd_printf_warning("[LUA] emu.register_frame is deprecated - please use emu.add_machine_frame_notifier\n"); register_function(func, "LUA_ON_FRAME"); }; emu["register_frame_done"] = [this] (sol::function func) { register_function(func, "LUA_ON_FRAME_DONE"); }; emu["register_sound_update"] = [this] (sol::function func) { register_function(func, "LUA_ON_SOUND_UPDATE"); }; emu["register_periodic"] = [this] (sol::function func) { register_function(func, "LUA_ON_PERIODIC"); }; @@ -678,7 +938,7 @@ void lua_engine::initialize() { mame_ui_manager &mui = mame_machine_manager::instance()->ui(); render_container &container = machine().render().ui_container(); - ui::menu_plugin::show_menu(mui, container, (char *)name); + ui::menu_plugin::show_menu(mui, container, name); }; emu["register_callback"] = [this] (sol::function cb, const std::string &name) @@ -686,10 +946,6 @@ void lua_engine::initialize() std::string field = "cb_" + name; sol().registry()[field] = cb; }; - emu["print_verbose"] = [] (const char *str) { osd_printf_verbose("%s\n", str); }; - emu["print_error"] = [] (const char *str) { osd_printf_error("%s\n", str); }; - emu["print_info"] = [] (const char *str) { osd_printf_info("%s\n", str); }; - emu["print_debug"] = [] (const char *str) { osd_printf_debug("%s\n", str); }; emu["osd_ticks"] = &osd_ticks; emu["osd_ticks_per_second"] = &osd_ticks_per_second; emu["driver_find"] = @@ -700,32 +956,13 @@ void lua_engine::initialize() return sol::lua_nil; return sol::make_object(s, driver_list::driver(i)); }; - emu["wait"] = lua_CFunction( - [] (lua_State *L) - { - lua_engine *engine = mame_machine_manager::instance()->lua(); - luaL_argcheck(L, lua_isnumber(L, 1), 1, "waiting duration expected"); - int ret = lua_pushthread(L); - if (ret == 1) - return luaL_error(L, "cannot wait from outside coroutine"); - int ref = luaL_ref(L, LUA_REGISTRYINDEX); - engine->machine().scheduler().timer_set(attotime::from_double(lua_tonumber(L, 1)), timer_expired_delegate(FUNC(lua_engine::resume), engine), ref, nullptr); - return lua_yield(L, 0); - }); - emu["lang_translate"] = sol::overload( - static_cast<char const *(*)(char const *)>(&lang_translate), - static_cast<char const *(*)(char const *, char const *)>(&lang_translate)); emu["pid"] = &osd_getpid; - emu["subst_env"] = - [] (const std::string &str) - { - std::string result; - osd_subst_env(result, str); - return result; - }; emu["device_enumerator"] = sol::overload( [] (device_t &dev) { return devenum<device_enumerator>(dev); }, [] (device_t &dev, int maxdepth) { return devenum<device_enumerator>(dev, maxdepth); }); + emu["palette_enumerator"] = sol::overload( + [] (device_t &dev) { return devenum<palette_interface_enumerator>(dev); }, + [] (device_t &dev, int maxdepth) { return devenum<palette_interface_enumerator>(dev, maxdepth); }); emu["screen_enumerator"] = sol::overload( [] (device_t &dev) { return devenum<screen_device_enumerator>(dev); }, [] (device_t &dev, int maxdepth) { return devenum<screen_device_enumerator>(dev, maxdepth); }); @@ -740,6 +977,10 @@ void lua_engine::initialize() [] (device_t &dev, int maxdepth) { return devenum<slot_interface_enumerator>(dev, maxdepth); }); + auto notifier_subscription_type = sol().registry().new_usertype<util::notifier_subscription>("notifier_subscription", sol::no_constructor); + notifier_subscription_type["unsubscribe"] = &util::notifier_subscription::reset; + notifier_subscription_type["is_active"] = sol::property(&util::notifier_subscription::operator bool); + auto attotime_type = emu.new_usertype<attotime>( "attotime", sol::call_constructor, sol::constructors<attotime(), attotime(seconds_t, attoseconds_t), attotime(attotime const &)>()); @@ -827,7 +1068,15 @@ void lua_engine::initialize() } new (&file) emu_file(path, flags); })); - file_type.set("read", [](emu_file &file, sol::buffer *buff) { buff->set_len(file.read(buff->get_ptr(), buff->get_len())); return buff; }); + file_type.set("read", + [] (emu_file &file, sol::this_state s, size_t len) + { + buffer_helper buf(s); + auto space = buf.prepare(len); + space.add(file.read(space.get(), len)); + buf.push(); + return sol::make_reference(s, sol::stack_reference(s, -1)); + }); file_type.set("write", [](emu_file &file, const std::string &data) { return file.write(data.data(), data.size()); }); file_type.set("puts", &emu_file::puts); file_type.set("open", static_cast<std::error_condition (emu_file::*)(std::string_view)>(&emu_file::open)); @@ -871,75 +1120,17 @@ void lua_engine::initialize() * thread runs until yield() and/or terminates on return. * thread:continue(val) - resume thread that has yielded and pass val to it * - * thread.result - get result of a terminated thread as string - * thread.busy - check if thread is running - * thread.yield - check if thread is yielded + * thread.result - get result of a terminated or yielding thread as string + * thread.busy - check if thread is running or yielding + * thread.yield - check if thread is yielding */ - auto thread_type = emu.new_usertype<context>("thread", sol::call_constructor, sol::constructors<sol::types<>>()); - thread_type.set("start", [](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()) - { - const char *tmp = ret.get<const char *>(); - if (tmp != nullptr) - ctx.result = tmp; - else - osd_printf_error("[LUA ERROR] in thread: return value must be string\n"); - } - else - { - sol::error err = ret; - osd_printf_error("[LUA ERROR] in thread: %s\n", err.what()); - } - } - else - { - sol::error err = res; - osd_printf_error("[LUA ERROR] when loading script for thread: %s\n", err.what()); - } - ctx.busy = false; - }); - ctx.busy = true; - ctx.yield = false; - th.detach(); - return true; - }); - thread_type.set("continue", [](context &ctx, const char *val) { - if (!ctx.yield) - return; - ctx.result = val; - ctx.sync.notify_all(); - }); - thread_type.set("result", sol::property([](context &ctx) -> std::string { - if (ctx.busy && !ctx.yield) - return ""; - return ctx.result; - })); - thread_type.set("busy", sol::readonly(&context::busy)); - thread_type.set("yield", sol::readonly(&context::yield)); + auto thread_type = emu.new_usertype<thread_context>("thread", sol::call_constructor, sol::constructors<sol::types<>>()); + thread_type.set_function("start", &thread_context::start); + thread_type.set_function("continue", &thread_context::resume); + thread_type["result"] = sol::property(&thread_context::result); + thread_type["busy"] = sol::property(&thread_context::busy); + thread_type["yield"] = sol::readonly(&thread_context::m_yield); /* save_item library @@ -971,12 +1162,14 @@ void lua_engine::initialize() })); item_type.set("size", sol::readonly(&save_item::size)); item_type.set("count", sol::readonly(&save_item::count)); - item_type.set("read", [this](save_item &item, int offset) -> sol::object { - if(!item.base || (offset >= item.count)) + item_type.set("read", + [this] (save_item &item, int offset) -> sol::object + { + if (!item.base || (offset >= item.count)) return sol::lua_nil; const void *const data = reinterpret_cast<const uint8_t *>(item.base) + (item.stride * (offset / item.valcount)); uint64_t ret = 0; - switch(item.size) + switch (item.size) { case 1: default: @@ -994,29 +1187,38 @@ void lua_engine::initialize() } return sol::make_object(sol(), ret); }); - item_type.set("read_block", [](save_item &item, int offset, sol::buffer *buff) { - if(!item.base || ((offset + buff->get_len()) > (item.size * item.count))) + item_type.set("read_block", + [] (save_item &item, sol::this_state s, uint32_t offset, size_t len) -> sol::object + { + if (!item.base) { - buff->set_len(0); + luaL_error(s, "Invalid save item"); + return sol::lua_nil; } - else + + if ((offset + len) > (item.size * item.count)) { - const uint32_t blocksize = item.size * item.valcount; - uint32_t remaining = buff->get_len(); - uint8_t *dest = reinterpret_cast<uint8_t *>(buff->get_ptr()); - while(remaining) - { - const uint32_t blockno = offset / blocksize; - const uint32_t available = blocksize - (offset % blocksize); - const uint32_t chunk = (available < remaining) ? available : remaining; - const void *const source = reinterpret_cast<const uint8_t *>(item.base) + (blockno * item.stride) + (offset % blocksize); - std::memcpy(dest, source, chunk); - offset += chunk; - remaining -= chunk; - dest += chunk; - } + luaL_error(s, "Range extends beyond end of save item"); + return sol::lua_nil; } - return buff; + + luaL_Buffer buff; + uint8_t *dest = reinterpret_cast<uint8_t *>(luaL_buffinitsize(s, &buff, len)); + const uint32_t blocksize = item.size * item.valcount; + size_t remaining = len; + while (remaining) + { + const uint32_t blockno = offset / blocksize; + const uint32_t available = blocksize - (offset % blocksize); + const uint32_t chunk = (available < remaining) ? available : remaining; + const void *const source = reinterpret_cast<const uint8_t *>(item.base) + (blockno * item.stride) + (offset % blocksize); + std::memcpy(dest, source, chunk); + offset += chunk; + remaining -= chunk; + dest += chunk; + } + luaL_pushresultsize(&buff, len); + return sol::make_reference(s, sol::stack_reference(s, -1)); }); item_type.set("write", [](save_item &item, int offset, uint64_t value) { if(!item.base || (offset >= item.count)) @@ -1134,25 +1336,25 @@ void lua_engine::initialize() auto core_options_entry_type = sol().registry().new_usertype<core_options::entry>("core_options_entry", "new", sol::no_constructor); core_options_entry_type.set("value", sol::overload( [this](core_options::entry &e, bool val) { - if(e.type() != OPTION_BOOLEAN) + if(e.type() != core_options::option_type::BOOLEAN) luaL_error(m_lua_state, "Cannot set option to wrong type"); else e.set_value(val ? "1" : "0", OPTION_PRIORITY_CMDLINE); }, [this](core_options::entry &e, float val) { - if(e.type() != OPTION_FLOAT) + if(e.type() != core_options::option_type::FLOAT) luaL_error(m_lua_state, "Cannot set option to wrong type"); else e.set_value(string_format("%f", val), OPTION_PRIORITY_CMDLINE); }, [this](core_options::entry &e, int val) { - if(e.type() != OPTION_INTEGER) + if(e.type() != core_options::option_type::INTEGER) luaL_error(m_lua_state, "Cannot set option to wrong type"); else e.set_value(string_format("%d", val), OPTION_PRIORITY_CMDLINE); }, [this](core_options::entry &e, const char *val) { - if(e.type() != OPTION_STRING) + if(e.type() != core_options::option_type::STRING && e.type() != core_options::option_type::PATH && e.type() != core_options::option_type::MULTIPATH) luaL_error(m_lua_state, "Cannot set option to wrong type"); else e.set_value(val, OPTION_PRIORITY_CMDLINE); @@ -1180,54 +1382,54 @@ void lua_engine::initialize() auto machine_type = sol().registry().new_usertype<running_machine>("machine", sol::no_constructor); - machine_type["exit"] = &running_machine::schedule_exit; - machine_type["hard_reset"] = &running_machine::schedule_hard_reset; - machine_type["soft_reset"] = &running_machine::schedule_soft_reset; - machine_type["save"] = &running_machine::schedule_save; // TODO: some kind of completion notification? - machine_type["load"] = &running_machine::schedule_load; // TODO: some kind of completion notification? - machine_type["buffer_save"] = - [] (running_machine &m, sol::this_state s) - { - // FIXME: this needs to schedule saving to a buffer and return asynchronously somehow - // right now it's broken by anonymous timers, synchronize, etc. - lua_State *L = s; - luaL_Buffer buff; - int size = ram_state::get_size(m.save()); - u8 *ptr = (u8 *)luaL_buffinitsize(L, &buff, size); - save_error error = m.save().write_buffer(ptr, size); - if (error == STATERR_NONE) + machine_type.set_function("exit", &running_machine::schedule_exit); + machine_type.set_function("hard_reset", &running_machine::schedule_hard_reset); + machine_type.set_function("soft_reset", &running_machine::schedule_soft_reset); + machine_type.set_function("save", &running_machine::schedule_save); // TODO: some kind of completion notification? + machine_type.set_function("load", &running_machine::schedule_load); // TODO: some kind of completion notification? + machine_type.set_function("buffer_save", + [] (running_machine &m, sol::this_state s) { - luaL_pushresultsize(&buff, size); - return sol::make_reference(L, sol::stack_reference(L, -1)); - } - luaL_error(L, "State save error."); - return sol::make_reference(L, nullptr); - }; - machine_type["buffer_load"] = - [] (running_machine &m, sol::this_state s, std::string str) - { - // FIXME: this needs to schedule loading from the buffer and return asynchronously somehow - // right now it's broken by anonymous timers, synchronize, etc. - save_error error = m.save().read_buffer((u8 *)str.data(), str.size()); - if (error == STATERR_NONE) + // FIXME: this needs to schedule saving to a buffer and return asynchronously somehow + // right now it's broken by anonymous timers, synchronize, etc. + lua_State *L = s; + luaL_Buffer buff; + int size = ram_state::get_size(m.save()); + u8 *ptr = (u8 *)luaL_buffinitsize(L, &buff, size); + save_error error = m.save().write_buffer(ptr, size); + if (error == STATERR_NONE) + { + luaL_pushresultsize(&buff, size); + return sol::make_reference(L, sol::stack_reference(L, -1)); + } + luaL_error(L, "State save error."); + return sol::make_reference(L, nullptr); + }); + machine_type.set_function("buffer_load", + [] (running_machine &m, sol::this_state s, std::string str) { - return true; - } - else + // FIXME: this needs to schedule loading from the buffer and return asynchronously somehow + // right now it's broken by anonymous timers, synchronize, etc. + save_error error = m.save().read_buffer((u8 *)str.data(), str.size()); + if (error == STATERR_NONE) + { + return true; + } + else + { + luaL_error(s,"State load error."); + return false; + } + }); + machine_type.set_function("popmessage", + [] (running_machine &m, std::optional<const char *> str) { - luaL_error(s,"State load error."); - return false; - } - }; - machine_type["popmessage"] = - [] (running_machine &m, const char *str) - { - if (str) - m.popmessage("%s", str); - else - m.popmessage(); - }; - machine_type["logerror"] = [] (running_machine &m, std::string const *str) { m.logerror("[luaengine] %s\n", str); }; + if (str) + m.popmessage("%s", *str); + else + m.popmessage(); + }); + machine_type.set_function("logerror", [] (running_machine &m, char const *str) { m.logerror("[luaengine] %s\n", str); }); machine_type["time"] = sol::property(&running_machine::time); machine_type["system"] = sol::property(&running_machine::system); machine_type["parameters"] = sol::property(&running_machine::parameters); @@ -1254,6 +1456,7 @@ void lua_engine::initialize() machine_type["exit_pending"] = sol::property(&running_machine::exit_pending); machine_type["hard_reset_pending"] = sol::property(&running_machine::hard_reset_pending); machine_type["devices"] = sol::property([] (running_machine &m) { return devenum<device_enumerator>(m.root_device()); }); + machine_type["palettes"] = sol::property([] (running_machine &m) { return devenum<palette_interface_enumerator>(m.root_device()); }); machine_type["screens"] = sol::property([] (running_machine &m) { return devenum<screen_device_enumerator>(m.root_device()); }); machine_type["cassettes"] = sol::property([] (running_machine &m) { return devenum<cassette_device_enumerator>(m.root_device()); }); machine_type["images"] = sol::property([] (running_machine &m) { return devenum<image_interface_enumerator>(m.root_device()); }); @@ -1267,7 +1470,7 @@ void lua_engine::initialize() game_driver_type["manufacturer"] = sol::readonly(&game_driver::manufacturer); game_driver_type["parent"] = sol::readonly(&game_driver::parent); game_driver_type["compatible_with"] = sol::property([] (game_driver const &driver) { return strcmp(driver.compatible_with, "0") ? driver.compatible_with : nullptr; }); - game_driver_type["source_file"] = sol::property([] (game_driver const &driver) { return &driver.type.source()[0]; }); + game_driver_type["source_file"] = sol::property([] (game_driver const &driver) { return driver.type.source(); }); game_driver_type["orientation"] = sol::property( [] (game_driver const &driver) { @@ -1294,35 +1497,11 @@ void lua_engine::initialize() } return rot; }); - game_driver_type["type"] = sol::property( - [] (game_driver const &driver) - { - // FIXME: this shouldn't be called type - there's potendial for confusion with the device type - // also, this should eventually go away in favour of richer flags - std::string type; - switch (driver.flags & machine_flags::MASK_TYPE) - { - case machine_flags::TYPE_ARCADE: - type = "arcade"; - break; - case machine_flags::TYPE_CONSOLE: - type = "console"; - break; - case machine_flags::TYPE_COMPUTER: - type = "computer"; - break; - default: - type = "other"; - break; - } - return type; - }); game_driver_type["not_working"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::NOT_WORKING) != 0; }); game_driver_type["supports_save"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::SUPPORTS_SAVE) != 0; }); game_driver_type["no_cocktail"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::NO_COCKTAIL) != 0; }); game_driver_type["is_bios_root"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::IS_BIOS_ROOT) != 0; }); game_driver_type["requires_artwork"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::REQUIRES_ARTWORK) != 0; }); - game_driver_type["clickable_artwork"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::CLICKABLE_ARTWORK) != 0; }); game_driver_type["unofficial"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::UNOFFICIAL) != 0; }); game_driver_type["no_sound_hw"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::NO_SOUND_HW) != 0; }); game_driver_type["mechanical"] = sol::property([] (game_driver const &driver) { return (driver.flags & machine_flags::MECHANICAL) != 0; }); @@ -1330,15 +1509,16 @@ void lua_engine::initialize() auto device_type = sol().registry().new_usertype<device_t>("device", sol::no_constructor); - device_type["subtag"] = &device_t::subtag; - device_type["siblingtag"] = &device_t::siblingtag; - device_type["memregion"] = &device_t::memregion; - device_type["memshare"] = &device_t::memshare; - device_type["membank"] = &device_t::membank; - device_type["ioport"] = &device_t::ioport; - device_type["subdevice"] = static_cast<device_t *(device_t::*)(std::string_view) const>(&device_t::subdevice); - device_type["siblingdevice"] = static_cast<device_t *(device_t::*)(std::string_view) const>(&device_t::siblingdevice); - device_type["parameter"] = &device_t::parameter; + device_type.set_function(sol::meta_function::to_string, [] (device_t &d) { return util::string_format("%s(%s)", d.shortname(), d.tag()); }); + device_type.set_function("subtag", &device_t::subtag); + device_type.set_function("siblingtag", &device_t::siblingtag); + device_type.set_function("memregion", &device_t::memregion); + device_type.set_function("memshare", &device_t::memshare); + device_type.set_function("membank", &device_t::membank); + device_type.set_function("ioport", &device_t::ioport); + device_type.set_function("subdevice", static_cast<device_t *(device_t::*)(std::string_view) const>(&device_t::subdevice)); + device_type.set_function("siblingdevice", static_cast<device_t *(device_t::*)(std::string_view) const>(&device_t::siblingdevice)); + device_type.set_function("parameter", &device_t::parameter); device_type["tag"] = sol::property(&device_t::tag); device_type["basetag"] = sol::property(&device_t::basetag); device_type["name"] = sol::property(&device_t::name); @@ -1367,18 +1547,13 @@ void lua_engine::initialize() } return sp_table; }); - // FIXME: improve this device_type["state"] = sol::property( - [this] (device_t &dev) + [] (device_t &dev, sol::this_state s) -> sol::object { - sol::table st_table = sol().create_table(); - const device_state_interface *state; - if(!dev.interface(state)) - return st_table; - // XXX: refrain from exporting non-visible entries? - for(auto &s : state->state_entries()) - st_table[s->symbol()] = s.get(); - return st_table; + device_state_interface const *state; + if (!dev.interface(state)) + return sol::lua_nil; + return sol::make_object(s, device_state_entries(*state)); }); // FIXME: turn into a wrapper - it's stupid slow to walk on every property access // also, this mixes up things like RAM areas with stuff saved by the device itself, so there's potential for key conflicts @@ -1414,11 +1589,67 @@ void lua_engine::initialize() }); + auto dipalette_type = sol().registry().new_usertype<device_palette_interface>("dipalette", sol::no_constructor); + dipalette_type.set_function("pen", &device_palette_interface::pen); + dipalette_type.set_function( + "pen_color", + [] (device_palette_interface const &pal, pen_t pen) + { + return uint32_t(pal.pen_color(pen)); + }); + dipalette_type.set_function("pen_contrast", &device_palette_interface::pen_contrast); + dipalette_type.set_function("pen_indirect", &device_palette_interface::pen_indirect); + dipalette_type.set_function( + "indirect_color", + [] (device_palette_interface const &pal, int index) + { + return uint32_t(pal.indirect_color(index)); + }); + dipalette_type["set_pen_color"] = sol::overload( + [] (device_palette_interface &pal, pen_t pen, uint32_t color) + { + pal.set_pen_color(pen, rgb_t(color)); + }, + static_cast<void (device_palette_interface::*)(pen_t, uint8_t, uint8_t, uint8_t)>(&device_palette_interface::set_pen_color)); + dipalette_type.set_function("set_pen_red_level", &device_palette_interface::set_pen_red_level); + dipalette_type.set_function("set_pen_green_level", &device_palette_interface::set_pen_green_level); + dipalette_type.set_function("set_pen_blue_level", &device_palette_interface::set_pen_blue_level); + dipalette_type.set_function("set_pen_contrast", &device_palette_interface::set_pen_contrast); + dipalette_type.set_function("set_pen_indirect", &device_palette_interface::set_pen_indirect); + dipalette_type["set_indirect_color"] = sol::overload( + [] (device_palette_interface &pal, int index, uint32_t color) + { + pal.set_indirect_color(index, rgb_t(color)); + }, + [] (device_palette_interface &pal, int index, uint8_t r, uint8_t g, uint8_t b) + { + pal.set_indirect_color(index, rgb_t(r, g, b)); + }); + dipalette_type.set_function("set_shadow_factor", &device_palette_interface::set_shadow_factor); + dipalette_type.set_function("set_highlight_factor", &device_palette_interface::set_highlight_factor); + dipalette_type.set_function("set_shadow_mode", &device_palette_interface::set_shadow_mode); + dipalette_type["palette"] = sol::property( + [] (device_palette_interface &pal) + { + return pal.palette() + ? std::optional<palette_wrapper>(std::in_place, *pal.palette()) + : std::optional<palette_wrapper>(); + }); + dipalette_type["entries"] = sol::property(&device_palette_interface::entries); + dipalette_type["indirect_entries"] = sol::property(&device_palette_interface::indirect_entries); + dipalette_type["black_pen"] = sol::property(&device_palette_interface::black_pen); + dipalette_type["white_pen"] = sol::property(&device_palette_interface::white_pen); + dipalette_type["shadows_enabled"] = sol::property(&device_palette_interface::shadows_enabled); + dipalette_type["highlights_enabled"] = sol::property(&device_palette_interface::hilights_enabled); + dipalette_type["device"] = sol::property(static_cast<device_t & (device_palette_interface::*)()>(&device_palette_interface::device)); + + auto screen_dev_type = sol().registry().new_usertype<screen_device>( "screen_dev", sol::no_constructor, sol::base_classes, sol::bases<device_t>()); - screen_dev_type["draw_box"] = + screen_dev_type.set_function( + "draw_box", [] (screen_device &sdev, float x1, float y1, float x2, float y2, std::optional<uint32_t> fgcolor, std::optional<uint32_t> bgcolor) { float const sc_width(sdev.visible_area().width()); @@ -1433,8 +1664,9 @@ void lua_engine::initialize() if (!bgcolor) bgcolor = ui.colors().background_color(); ui.draw_outlined_box(sdev.container(), x1, y1, x2, y2, *fgcolor, *bgcolor); - }; - screen_dev_type["draw_line"] = + }); + screen_dev_type.set_function( + "draw_line", [] (screen_device &sdev, float x1, float y1, float x2, float y2, std::optional<uint32_t> color) { float const sc_width(sdev.visible_area().width()); @@ -1446,8 +1678,9 @@ void lua_engine::initialize() if (!color) color = mame_machine_manager::instance()->ui().colors().text_color(); sdev.container().add_line(x1, y1, x2, y2, UI_LINE_WIDTH, rgb_t(*color), PRIMFLAG_BLENDMODE(BLENDMODE_ALPHA)); - }; - screen_dev_type["draw_text"] = + }); + screen_dev_type.set_function( + "draw_text", [this] (screen_device &sdev, sol::object xobj, float y, char const *msg, std::optional<uint32_t> fgcolor, std::optional<uint32_t> bgcolor) { float const sc_width(sdev.visible_area().width()); @@ -1485,85 +1718,87 @@ void lua_engine::initialize() x, y, (1.0f - x), justify, ui::text_layout::word_wrapping::WORD, mame_ui_manager::OPAQUE_, *fgcolor, *bgcolor); - }; - screen_dev_type["orientation"] = - [] (screen_device &sdev) - { - uint32_t flags = sdev.orientation(); - int rotation_angle = 0; - switch (flags) + }); + screen_dev_type.set_function( + "orientation", + [] (screen_device &sdev) { - case ORIENTATION_SWAP_XY: - case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X: - rotation_angle = 90; - flags ^= ORIENTATION_FLIP_X; - break; - case ORIENTATION_FLIP_Y: - case ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: - rotation_angle = 180; - flags ^= ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y; - break; - case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_Y: - case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: - rotation_angle = 270; - flags ^= ORIENTATION_FLIP_Y; - break; - } - return std::tuple<int, bool, bool>(rotation_angle, flags & ORIENTATION_FLIP_X, flags & ORIENTATION_FLIP_Y); - }; + uint32_t flags = sdev.orientation(); + int rotation_angle = 0; + switch (flags) + { + case ORIENTATION_SWAP_XY: + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X: + rotation_angle = 90; + flags ^= ORIENTATION_FLIP_X; + break; + case ORIENTATION_FLIP_Y: + case ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + rotation_angle = 180; + flags ^= ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y; + break; + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_Y: + case ORIENTATION_SWAP_XY | ORIENTATION_FLIP_X | ORIENTATION_FLIP_Y: + rotation_angle = 270; + flags ^= ORIENTATION_FLIP_Y; + break; + } + return std::tuple<int, bool, bool>(rotation_angle, flags & ORIENTATION_FLIP_X, flags & ORIENTATION_FLIP_Y); + }); screen_dev_type["time_until_pos"] = sol::overload( [] (screen_device &sdev, int vpos) { return sdev.time_until_pos(vpos).as_double(); }, [] (screen_device &sdev, int vpos, int hpos) { return sdev.time_until_pos(vpos, hpos).as_double(); }); - screen_dev_type["time_until_vblank_start"] = &screen_device::time_until_vblank_start; - screen_dev_type["time_until_vblank_end"] = &screen_device::time_until_vblank_end; - screen_dev_type["snapshot"] = - [this] (screen_device &sdev, char const *filename) -> sol::object - { - // FIXME: this shouldn't be a member of the screen device - // the screen is only used as a hint when configured for native snapshots and may be ignored - std::string snapstr; - bool is_absolute_path = false; - if (filename) + screen_dev_type.set_function("time_until_vblank_start", &screen_device::time_until_vblank_start); + screen_dev_type.set_function("time_until_vblank_end", &screen_device::time_until_vblank_end); + screen_dev_type.set_function( + "snapshot", + [this] (screen_device &sdev, char const *filename) -> sol::object { - // a filename was specified; if it isn't absolute post-process it - snapstr = process_snapshot_filename(machine(), filename); - is_absolute_path = osd_is_absolute_path(snapstr); - } + // FIXME: this shouldn't be a member of the screen device + // the screen is only used as a hint when configured for native snapshots and may be ignored + std::string snapstr; + bool is_absolute_path = false; + if (filename) + { + // a filename was specified; if it isn't absolute post-process it + snapstr = process_snapshot_filename(machine(), filename); + is_absolute_path = osd_is_absolute_path(snapstr); + } - // open the file - emu_file file(is_absolute_path ? "" : machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); - std::error_condition filerr; - if (!snapstr.empty()) - filerr = file.open(snapstr); - else - filerr = machine().video().open_next(file, "png"); - if (filerr) - return sol::make_object(sol(), filerr); + // open the file + emu_file file(is_absolute_path ? "" : machine().options().snapshot_directory(), OPEN_FLAG_WRITE | OPEN_FLAG_CREATE | OPEN_FLAG_CREATE_PATHS); + std::error_condition filerr; + if (!snapstr.empty()) + filerr = file.open(snapstr); + else + filerr = machine().video().open_next(file, "png"); + if (filerr) + return sol::make_object(sol(), filerr); - // and save the snapshot - machine().video().save_snapshot(&sdev, file); - return sol::lua_nil; - }; - screen_dev_type["pixel"] = [] (screen_device &sdev, s32 x, s32 y) { return sdev.pixel(x, y); }; - screen_dev_type["pixels"] = - [] (screen_device &sdev, sol::this_state s) - { - // TODO: would be better if this could return a tuple of (buffer, width, height) - const rectangle &visarea = sdev.visible_area(); - luaL_Buffer buff; - int size = visarea.height() * visarea.width() * 4; - u32 *ptr = (u32 *)luaL_buffinitsize(s, &buff, size); - sdev.pixels(ptr); - luaL_pushresultsize(&buff, size); - return sol::make_reference(s, sol::stack_reference(s, -1)); - }; + // and save the snapshot + machine().video().save_snapshot(&sdev, file); + return sol::lua_nil; + }); + screen_dev_type.set_function("pixel", &screen_device::pixel); + screen_dev_type.set_function( + "pixels", + [] (screen_device &sdev, sol::this_state s) + { + const rectangle &visarea = sdev.visible_area(); + luaL_Buffer buff; + int size = visarea.height() * visarea.width() * 4; + u32 *const ptr = reinterpret_cast<u32 *>(luaL_buffinitsize(s, &buff, size)); + sdev.pixels(ptr); + luaL_pushresultsize(&buff, size); + return std::make_tuple(sol::make_reference(s, sol::stack_reference(s, -1)), visarea.width(), visarea.height()); + }); screen_dev_type["screen_type"] = sol::property(&screen_device::screen_type); screen_dev_type["width"] = sol::property([] (screen_device &sdev) { return sdev.visible_area().width(); }); screen_dev_type["height"] = sol::property([] (screen_device &sdev) { return sdev.visible_area().height(); }); screen_dev_type["refresh"] = sol::property([] (screen_device &sdev) { return ATTOSECONDS_TO_HZ(sdev.refresh_attoseconds()); }); screen_dev_type["refresh_attoseconds"] = sol::property([] (screen_device &sdev) { return sdev.refresh_attoseconds(); }); - screen_dev_type["xofffset"] = sol::property(&screen_device::xoffset); - screen_dev_type["yofffset"] = sol::property(&screen_device::yoffset); + screen_dev_type["xoffset"] = sol::property(&screen_device::xoffset); + screen_dev_type["yoffset"] = sol::property(&screen_device::yoffset); screen_dev_type["xscale"] = sol::property(&screen_device::xscale); screen_dev_type["yscale"] = sol::property(&screen_device::yscale); screen_dev_type["pixel_period"] = sol::property([] (screen_device &sdev) { return sdev.pixel_period().as_double(); }); @@ -1571,6 +1806,7 @@ void lua_engine::initialize() screen_dev_type["frame_period"] = sol::property([] (screen_device &sdev) { return sdev.frame_period().as_double(); }); screen_dev_type["frame_number"] = &screen_device::frame_number; screen_dev_type["container"] = sol::property(&screen_device::container); + screen_dev_type["palette"] = sol::property([] (screen_device const &sdev) { return sdev.has_palette() ? &sdev.palette() : nullptr; }); auto cass_type = sol().registry().new_usertype<cassette_image_device>( @@ -1593,11 +1829,65 @@ void lua_engine::initialize() auto image_type = sol().registry().new_usertype<device_image_interface>("image", sol::no_constructor); - image_type["load"] = &device_image_interface::load; - image_type["load_software"] = static_cast<image_init_result (device_image_interface::*)(std::string_view)>(&device_image_interface::load_software); - image_type["unload"] = &device_image_interface::unload; - image_type["create"] = static_cast<image_init_result (device_image_interface::*)(std::string_view)>(&device_image_interface::create); - image_type["display"] = &device_image_interface::call_display; + image_type.set_function("load", + [] (device_image_interface &di, sol::this_state s, std::string_view path) -> sol::object + { + auto [err, message] = di.load(path); + if (!err) + return sol::lua_nil; + else if (!message.empty()) + return sol::make_object(s, message); + else + return sol::make_object(s, err.message()); + }); + image_type.set_function("load_software", + [] (device_image_interface &di, sol::this_state s, std::string_view identifier) -> sol::object + { + auto [err, message] = di.load_software(identifier); + if (!err) + return sol::lua_nil; + else if (!message.empty()) + return sol::make_object(s, message); + else + return sol::make_object(s, err.message()); + }); + image_type.set_function("unload", &device_image_interface::unload); + image_type.set_function("create", + [] (device_image_interface &di, sol::this_state s, std::string_view path) -> sol::object + { + auto [err, message] = di.create(path); + if (!err) + return sol::lua_nil; + else if (!message.empty()) + return sol::make_object(s, message); + else + return sol::make_object(s, err.message()); + }); + image_type.set_function("display", &device_image_interface::call_display); + image_type.set_function("add_media_change_notifier", + [this] (device_image_interface &di, sol::protected_function cb) + { + return di.add_media_change_notifier( + [this, cbfunc = sol::protected_function(m_lua_state, cb)] (device_image_interface::media_change_event ev) + { + char const *evstr(nullptr); + switch (ev) + { + case device_image_interface::media_change_event::LOADED: + evstr = "loaded"; + break; + case device_image_interface::media_change_event::UNLOADED: + evstr = "unloaded"; + break; + } + auto status(invoke(cbfunc, evstr)); + if (!status.valid()) + { + auto err(status.template get<sol::error>()); + osd_printf_error("[LUA ERROR] error in media change callback: %s\n", err.what()); + } + }); + }); image_type["is_readable"] = sol::property(&device_image_interface::is_readable); image_type["is_writeable"] = sol::property(&device_image_interface::is_writeable); image_type["is_creatable"] = sol::property(&device_image_interface::is_creatable); @@ -1640,6 +1930,38 @@ void lua_engine::initialize() image_type["device"] = sol::property(static_cast<device_t & (device_image_interface::*)()>(&device_image_interface::device)); + auto state_entry_type = sol().registry().new_usertype<device_state_entry>("state_entry", sol::no_constructor); + state_entry_type["value"] = sol::property( + [] (device_state_entry const &entry, sol::this_state s) -> sol::object + { + if (entry.is_float()) + return sol::make_object(s, entry.dvalue()); + else + return sol::make_object(s, entry.value()); + }, + [] (device_state_entry const &entry, sol::this_state s, sol::object value) + { + if (!entry.writeable()) + luaL_error(s, "cannot set value of read-only device state entry"); + else if (entry.is_float()) + entry.set_dvalue(value.as<double>()); + else + entry.set_value(value.as<u64>()); + }); + state_entry_type["symbol"] = sol::property(&device_state_entry::symbol); + state_entry_type["visible"] = sol::property(&device_state_entry::visible); + state_entry_type["writeable"] = sol::property(&device_state_entry::writeable); + state_entry_type["is_float"] = sol::property(&device_state_entry::is_float); + state_entry_type["datamask"] = sol::property( + [] (device_state_entry const &entry) + { + return entry.is_float() ? std::optional<u64>() : std::optional<u64>(entry.datamask()); + }); + state_entry_type["datasize"] = sol::property(&device_state_entry::datasize); + state_entry_type["max_length"] = sol::property(&device_state_entry::max_length); + state_entry_type[sol::meta_function::to_string] = &device_state_entry::to_string; + + auto format_type = sol().registry().new_usertype<image_device_format>("image_format", sol::no_constructor); format_type["name"] = sol::property(&image_device_format::name); format_type["description"] = sol::property(&image_device_format::description); @@ -1760,54 +2082,21 @@ void lua_engine::initialize() auto ui_type = sol().registry().new_usertype<mame_ui_manager>("ui", sol::no_constructor); // sol converts char32_t to a string - ui_type["get_char_width"] = [] (mame_ui_manager &m, uint32_t utf8char) { return m.get_char_width(utf8char); }; - ui_type["get_string_width"] = &mame_ui_manager::get_string_width; - ui_type["set_aggressive_input_focus"] = [](mame_ui_manager &m, bool aggressive_focus) { osd_set_aggressive_input_focus(aggressive_focus); }; + ui_type.set_function("get_char_width", [] (mame_ui_manager &m, uint32_t utf8char) { return m.get_char_width(utf8char); }); + ui_type.set_function("get_string_width", static_cast<float (mame_ui_manager::*)(std::string_view)>(&mame_ui_manager::get_string_width)); + ui_type.set_function("set_aggressive_input_focus", [] (mame_ui_manager &m, bool aggressive_focus) { osd_set_aggressive_input_focus(aggressive_focus); }); ui_type["get_general_input_setting"] = sol::overload( // TODO: overload with sequence type string - parser isn't available here [] (mame_ui_manager &ui, ioport_type type, int player) { return ui.get_general_input_setting(type, player, SEQ_TYPE_STANDARD); }, [] (mame_ui_manager &ui, ioport_type type) { return ui.get_general_input_setting(type, 0, SEQ_TYPE_STANDARD); }); ui_type["options"] = sol::property([] (mame_ui_manager &m) { return static_cast<core_options *>(&m.options()); }); - ui_type["line_height"] = sol::property(&mame_ui_manager::get_line_height); + ui_type["line_height"] = sol::property([] (mame_ui_manager &m) { return m.get_line_height(); }); ui_type["menu_active"] = sol::property(&mame_ui_manager::is_menu_active); + ui_type["ui_active"] = sol::property(&mame_ui_manager::ui_active, &mame_ui_manager::set_ui_active); ui_type["single_step"] = sol::property(&mame_ui_manager::single_step, &mame_ui_manager::set_single_step); ui_type["show_fps"] = sol::property(&mame_ui_manager::show_fps, &mame_ui_manager::set_show_fps); ui_type["show_profiler"] = sol::property(&mame_ui_manager::show_profiler, &mame_ui_manager::set_show_profiler); - - -/* device_state_entry library - * - * manager:machine().devices[device_tag].state[state_name] - * - * state:name() - get device state name - * state:is_visible() - is state visible in debugger - * state:is_divider() - is state a divider - * - * state.value - get device state value - */ - - auto dev_state_type = sol().registry().new_usertype<device_state_entry>("dev_state", "new", sol::no_constructor); - dev_state_type.set("name", &device_state_entry::symbol); - dev_state_type.set("value", sol::property( - [this](device_state_entry &entry) -> uint64_t { - device_state_interface *state = entry.parent_state(); - if(state) - { - machine().save().dispatch_presave(); - return state->state_int(entry.index()); - } - return 0; - }, - [this](device_state_entry &entry, uint64_t val) { - device_state_interface *state = entry.parent_state(); - if(state) - { - state->set_state_int(entry.index(), val); - machine().save().dispatch_presave(); - } - })); - dev_state_type.set("is_visible", &device_state_entry::visible); - dev_state_type.set("is_divider", &device_state_entry::divider); + ui_type["image_display_enabled"] = sol::property(&mame_ui_manager::image_display_enabled, &mame_ui_manager::set_image_display_enabled); /* rom_entry library @@ -1875,6 +2164,10 @@ void lua_engine::initialize() //------------------------------------------------- bool lua_engine::frame_hook() { + std::vector<int> tasks = std::move(m_update_tasks); + m_update_tasks.clear(); + resume_tasks(m_lua_state, tasks, true); // TODO: doesn't need to return anything + return execute_function("LUA_ON_FRAME_DONE"); } @@ -1884,6 +2177,10 @@ bool lua_engine::frame_hook() void lua_engine::close() { + m_notifiers.reset(); + m_menu.clear(); + m_update_tasks.clear(); + m_frame_tasks.clear(); m_sol_state.reset(); if (m_lua_state) { @@ -1893,49 +2190,46 @@ void lua_engine::close() } } -void lua_engine::resume(void *ptr, int nparam) +void lua_engine::resume(s32 param) { - lua_rawgeti(m_lua_state, LUA_REGISTRYINDEX, nparam); - lua_State *L = lua_tothread(m_lua_state, -1); - lua_pop(m_lua_state, 1); - int stat = lua_resume(L, nullptr, 0); - if((stat != LUA_OK) && (stat != LUA_YIELD)) - { - osd_printf_error("[LUA ERROR] in resume: %s\n", lua_tostring(L, -1)); - lua_pop(L, 1); - } - luaL_unref(m_lua_state, LUA_REGISTRYINDEX, nparam); + attotime const now = machine().time(); + auto const pos = std::find_if( + m_waiting_tasks.begin(), + m_waiting_tasks.end(), + [&now] (auto const &x) { return now < x.first; }); + std::vector<int> expired; + expired.reserve(std::distance(m_waiting_tasks.begin(), pos)); + for (auto it = m_waiting_tasks.begin(); pos != it; ++it) + expired.emplace_back(it->second); + m_waiting_tasks.erase(m_waiting_tasks.begin(), pos); + if (!m_waiting_tasks.empty()) + m_timer->reset(m_waiting_tasks.begin()->first - now); + resume_tasks(m_lua_state, expired, true); } -void lua_engine::run(sol::load_result res) +//------------------------------------------------- +// load_script - load script from file path +//------------------------------------------------- + +sol::load_result lua_engine::load_script(std::string const &filename) { - if(res.valid()) - { - auto ret = invoke(res.get<sol::protected_function>()); - if(!ret.valid()) - { - sol::error err = ret; - osd_printf_error("[LUA ERROR] in run: %s\n", err.what()); - } - } - else - osd_printf_error("[LUA ERROR] %d loading Lua script\n", (int)res.status()); + return sol().load_file(filename); } //------------------------------------------------- -// execute - load and execute script +// load_string - load script from string //------------------------------------------------- -void lua_engine::load_script(const char *filename) +sol::load_result lua_engine::load_string(std::string const &value) { - run(sol().load_file(filename)); + return sol().load(value); } //------------------------------------------------- -// execute_string - execute script from string +// make_environment - make a sandbox //------------------------------------------------- -void lua_engine::load_string(const char *value) +sol::environment lua_engine::make_environment() { - run(sol().load(value)); + return sol::environment(sol(), sol::create, sol().globals()); } |