18
0
mirror of https://github.com/MaSzyna-EU07/maszyna.git synced 2026-11-14 06:53:27 +01:00
Files
maszyna/scripting/luaprogram.cpp

848 lines
23 KiB
C++

/*
This Source Code Form is subject to the
terms of the Mozilla Public License, v.
2.0. If a copy of the MPL was not
distributed with this file, You can
obtain one at
http://mozilla.org/MPL/2.0/.
*/
// the Lua screens: scripts run by LuaJIT, each screen in a state of its own, with the eu07 table of
// script_screens_doc.md. the state has no io, os, ffi, debug or require, reads files only through eu07 and below the
// script's folder, takes no precompiled code, and runs without the JIT compiler, so that the instruction count hook
// can stop a script stuck in a loop (LuaJIT calls no hooks in compiled code)
#include "stdafx.h"
#include "scripting/scriptscreenprogram.h"
#include "scripting/scriptscreendrawing.h"
#include "scripting/scriptscreenkernels.h"
#include "utilities/Logs.h"
#include "utilities/dictionary.h"
#include "utilities/utilities.h"
#include "stb/stb_image.h"
#include <lua.hpp>
#include <algorithm>
#include <array>
#include <cstring>
#include <format>
#include <fstream>
#include <iterator>
#include <new>
#include <string_view>
namespace script_screens
{
namespace
{
constexpr int hook_interval{1000};
constexpr char const *image_metatable{"eu07.image"};
constexpr char const *font_metatable{"eu07.font"};
constexpr char const *sprite_metatable{"eu07.sprite"};
// the address is the key of the program in the registry of its state
constexpr char program_key{0};
class lua_program final : public program
{
public:
lua_program(std::string const &Script, script_screen_kind const Kind) : kind{Kind}, script{Script}, folder{substr_path(Script)} {}
~lua_program() override
{
if (m_state != nullptr)
{
lua_close(m_state);
}
}
lua_program(lua_program const &) = delete;
lua_program &operator=(lua_program const &) = delete;
bool start();
bool render(render_call &Call) override;
script_screen_kind const kind;
std::string const script;
std::string const folder;
render_call *call{nullptr}; // the refresh in progress
bool commands_ignored{false}; // a screen outside the cab tried to send a command, and it was logged
std::int64_t budget{0}; // instructions left to the call in progress
private:
// calls what's on the stack, with the instruction limit. logs a failure
bool run(int Arguments, int Results, std::string_view What);
void open_libraries();
void register_api();
lua_State *m_state{nullptr};
int m_screen{LUA_NOREF}; // the table the script returned
};
lua_program *program_of(lua_State *L)
{
lua_pushlightuserdata(L, const_cast<char *>(&program_key)); // NOSONAR: the address is only a key
lua_rawget(L, LUA_REGISTRYINDEX);
auto *self{static_cast<lua_program *>(lua_touserdata(L, -1))};
lua_pop(L, 1);
return self;
}
void count_hook(lua_State *L, lua_Debug * /*Debug*/)
{
auto *self{program_of(L)};
if (self == nullptr)
{
return;
}
self->budget -= hook_interval;
if (self->budget < 0)
{
luaL_error(L, "instruction limit exceeded");
}
}
// objects of the api -------------------------------------------------------------------------------------------------
template <typename Type_> Type_ *push_object(lua_State *L, Type_ &&Object, char const *Metatable)
{
auto *memory{lua_newuserdata(L, sizeof(Type_))};
auto *object{new (memory) Type_(std::move(Object))};
luaL_getmetatable(L, Metatable);
lua_setmetatable(L, -2);
return object;
}
template <typename Type_> int collect(lua_State *L)
{
static_cast<Type_ *>(lua_touserdata(L, 1))->~Type_();
return 0;
}
// the object at the index when it has the metatable, otherwise nullptr
template <typename Type_> Type_ *test_object(lua_State *L, int const Index, char const *Metatable)
{
auto *object{lua_touserdata(L, Index)};
if (object == nullptr || 0 == lua_getmetatable(L, Index))
{
return nullptr;
}
luaL_getmetatable(L, Metatable);
auto const matches{lua_rawequal(L, -1, -2) != 0};
lua_pop(L, 2);
return matches ? static_cast<Type_ *>(object) : nullptr;
}
template <typename Type_> Type_ &check_object(lua_State *L, int const Index, char const *Metatable)
{
auto *object{test_object<Type_>(L, Index, Metatable)};
if (object == nullptr)
{
luaL_typerror(L, Index, Metatable);
}
return *object;
}
canvas &check_image(lua_State *L, int const Index)
{
return check_object<canvas>(L, Index, image_metatable);
}
int check_int(lua_State *L, int const Index)
{
return static_cast<int>(luaL_checkinteger(L, Index));
}
int optional_int(lua_State *L, int const Index, int const Default)
{
return static_cast<int>(luaL_optinteger(L, Index, Default));
}
// a colour: { r, g, b }, each 0-255
rgb check_color(lua_State *L, int const Index)
{
luaL_checktype(L, Index, LUA_TTABLE);
rgb color{};
for (int channel = 0; channel < 3; ++channel)
{
lua_rawgeti(L, Index, channel + 1);
if (0 == lua_isnumber(L, -1))
{
luaL_argerror(L, Index, "a colour is { r, g, b }");
}
color[channel] = static_cast<std::uint8_t>(std::clamp<lua_Integer>(lua_tointeger(L, -1), 0, 255));
lua_pop(L, 1);
}
return color;
}
std::string_view check_text(lua_State *L, int const Index)
{
std::size_t length{0};
auto const *text{luaL_checklstring(L, Index, &length)};
return {text, length};
}
struct decoded_image
{
std::vector<std::uint8_t> pixels;
int width{0};
int height{0};
};
// a .png of the screen's folder or below, as RGBA
std::optional<decoded_image> read_png(std::string const &Folder, std::string_view const Path)
{
auto const file{read_screen_file(Folder, Path)};
if (false == file.has_value())
{
return std::nullopt;
}
int width{0};
int height{0};
int channels{0};
// the texture loader turns every image bottom row first, for all threads (model/Texture.cpp): fonts and sprites are
// read top row first, as led_util reads them
stbi_set_flip_vertically_on_load_thread(0);
auto *pixels{stbi_load_from_memory(reinterpret_cast<stbi_uc const *>(file->data()), static_cast<int>(file->size()), &width, &height, &channels, 4)};
if (pixels == nullptr)
{
return std::nullopt;
}
decoded_image result{std::vector<std::uint8_t>(pixels, pixels + image_size(width, height)), width, height};
stbi_image_free(pixels);
return result;
}
// eu07 ---------------------------------------------------------------------------------------------------------------
// eu07.image(width, height [, color]): a new image, black or in the colour
int api_image(lua_State *L)
{
auto const width{check_int(L, 1)};
auto const height{check_int(L, 2)};
luaL_argcheck(L, valid_image_side(width), 1, "between 1 and 4096");
luaL_argcheck(L, valid_image_side(height), 2, "between 1 and 4096");
auto const color{lua_isnoneornil(L, 3) ? rgb{0, 0, 0} : check_color(L, 3)};
push_object(L, canvas{width, height, color}, image_metatable);
return 1;
}
// eu07.font(path): the font of path .. ".png" and path .. ".txt", below the script's folder
int api_font(lua_State *L)
{
auto const path{check_text(L, 1)};
auto const *self{program_of(L)};
auto loaded{false};
{
auto const image{read_png(self->folder, std::string{path} + ".png")};
auto const description{read_screen_file(self->folder, std::string{path} + ".txt")};
if (image.has_value() && description.has_value())
{
if (auto font{bitmap_font::parse(image->pixels, image->width, image->height, *description)}; font.has_value())
{
push_object(L, std::move(*font), font_metatable);
loaded = true;
}
}
}
if (false == loaded)
{
return luaL_error(L, "font \"%s\" not found below the script's folder", lua_tostring(L, 1));
}
return 1;
}
// eu07.sprite(path): the sprite of path .. ".png", below the script's folder
int api_sprite(lua_State *L)
{
auto const path{check_text(L, 1)};
auto const *self{program_of(L)};
auto loaded{false};
{
if (auto const image{read_png(self->folder, std::string{path} + ".png")}; image.has_value())
{
push_object(L, bitmap_font::sprite(image->pixels, image->width, image->height), sprite_metatable);
loaded = true;
}
}
if (false == loaded)
{
return luaL_error(L, "sprite \"%s\" not found below the script's folder", lua_tostring(L, 1));
}
return 1;
}
int api_time(lua_State *L)
{
lua_pushnumber(L, screen_time());
return 1;
}
// eu07.log(...): the values, joined with spaces, as print() does
int api_log(lua_State *L)
{
auto const count{lua_gettop(L)};
for (int idx = 1; idx <= count; ++idx)
{
if (idx > 1)
{
lua_pushliteral(L, " ");
}
lua_getglobal(L, "tostring");
lua_pushvalue(L, idx);
lua_call(L, 1, 1);
}
if (count == 0)
{
lua_pushliteral(L, "");
}
else
{
lua_concat(L, count * 2 - 1);
}
auto const *self{program_of(L)};
auto const *text{lua_tostring(L, -1)};
WriteLog(std::format("Lua screen \"{}\": {}", self->script, text != nullptr ? text : ""));
return 0;
}
// eu07.command(name, param1, param2): sends a command to the vehicle of the cab
int api_command(lua_State *L)
{
auto const name{check_text(L, 1)};
auto const param1{optional_int(L, 2, 0)};
auto const param2{optional_int(L, 3, 0)};
auto *self{program_of(L)};
if (self->call == nullptr)
{
return 0;
}
if (self->kind != script_screen_kind::cab)
{
if (false == self->commands_ignored)
{
self->commands_ignored = true;
WriteLog(std::format("Lua screen \"{}\": commands are only taken from screens in the cab", self->script));
}
return 0;
}
self->call->commands.emplace_back(std::string{name}, param1, param2, self->call->vehicle);
return 0;
}
// eu07.read_file(path): the contents of a file below the script's folder, or nil
int api_read_file(lua_State *L)
{
auto const path{check_text(L, 1)};
auto const *self{program_of(L)};
if (auto const contents{read_screen_file(self->folder, path)}; contents.has_value())
{
lua_pushlstring(L, contents->data(), contents->size());
}
else
{
lua_pushnil(L);
}
return 1;
}
// loads a script as text: precompiled code is refused. leaves the chunk, or an error message, on the stack
bool load_chunk(lua_State *L, std::string_view const Code, std::string const &Name)
{
if (false == Code.empty() && Code.front() == '\x1b')
{
lua_pushfstring(L, "%s: precompiled scripts aren't loaded", Name.c_str());
return false;
}
return 0 == luaL_loadbufferx(L, Code.data(), Code.size(), ("@" + Name).c_str(), "t");
}
// eu07.include(path): runs another script below the script's folder and returns what it returns
int api_include(lua_State *L)
{
auto const path{check_text(L, 1)};
auto const *self{program_of(L)};
auto loaded{false};
{
if (auto const code{read_screen_file(self->folder, path)}; code.has_value())
{
loaded = load_chunk(L, *code, self->folder + std::string{path});
}
else
{
lua_pushfstring(L, "script \"%s\" not found below the script's folder", lua_tostring(L, 1));
}
}
if (false == loaded)
{
return lua_error(L);
}
lua_call(L, 0, 1);
return 1;
}
// eu07.image methods -------------------------------------------------------------------------------------------------
int image_width(lua_State *L)
{
lua_pushinteger(L, check_image(L, 1).width());
return 1;
}
int image_height(lua_State *L)
{
lua_pushinteger(L, check_image(L, 1).height());
return 1;
}
int image_fill(lua_State *L)
{
auto &image{check_image(L, 1)};
image.fill(check_color(L, 2));
return 0;
}
int image_set(lua_State *L)
{
auto &image{check_image(L, 1)};
auto const x{check_int(L, 2)};
auto const y{check_int(L, 3)};
image.set(x, y, check_color(L, 4));
return 0;
}
int image_get(lua_State *L)
{
auto const &image{check_image(L, 1)};
auto const color{image.get(check_int(L, 2), check_int(L, 3))};
for (auto const channel : color)
{
lua_pushinteger(L, channel);
}
return 3;
}
int image_fill_rect(lua_State *L)
{
auto &image{check_image(L, 1)};
auto const x{check_int(L, 2)};
auto const y{check_int(L, 3)};
auto const width{check_int(L, 4)};
auto const height{check_int(L, 5)};
image.fill_rect(x, y, width, height, check_color(L, 6));
return 0;
}
enum class alignment
{
left,
right,
center
};
// image:draw_text(font, x, y, text, color [, spacing]): returns the x after the text
int draw_text_aligned(lua_State *L, alignment const Alignment)
{
auto &image{check_image(L, 1)};
auto const &font{check_object<bitmap_font>(L, 2, font_metatable)};
auto x{check_int(L, 3)};
auto const y{check_int(L, 4)};
auto const text{check_text(L, 5)};
auto const color{check_color(L, 6)};
auto const spacing{optional_int(L, 7, 1)};
if (Alignment == alignment::right)
{
x -= font.text_width(text, spacing);
}
else if (Alignment == alignment::center)
{
x -= font.text_width(text, spacing) / 2;
}
lua_pushinteger(L, image.draw_text(font, x, y, text, color, spacing));
return 1;
}
int image_draw_text(lua_State *L)
{
return draw_text_aligned(L, alignment::left);
}
int image_draw_text_right(lua_State *L)
{
return draw_text_aligned(L, alignment::right);
}
int image_draw_text_centered(lua_State *L)
{
return draw_text_aligned(L, alignment::center);
}
int image_draw_sprite(lua_State *L)
{
auto &image{check_image(L, 1)};
auto const &sprite{check_object<glyph>(L, 2, sprite_metatable)};
auto const x{check_int(L, 3)};
auto const y{check_int(L, 4)};
image.draw_glyph(sprite, x, y, check_color(L, 5));
return 0;
}
int image_draw_image(lua_State *L)
{
auto &image{check_image(L, 1)};
auto const &source{check_image(L, 2)};
image.draw_image(source, check_int(L, 3), check_int(L, 4));
return 0;
}
// image:led_post([bloom]): led_util's render_post, a new image (2w + 2) x (2h + 2)
int image_led_post(lua_State *L)
{
auto const &image{check_image(L, 1)};
auto const bloom{lua_isnoneornil(L, 2) || lua_toboolean(L, 2) != 0};
luaL_argcheck(L, valid_image_side(image.width() * 2 + 2) && valid_image_side(image.height() * 2 + 2), 1, "too large");
std::vector<std::uint8_t> pixels(image_size(image.width() * 2 + 2, image.height() * 2 + 2));
led_post(image.pixels(), image.width(), image.height(), pixels, bloom);
push_object(L, canvas{image.width() * 2 + 2, image.height() * 2 + 2, std::move(pixels)}, image_metatable);
return 1;
}
// image:resize(width, height): scaled as Pillow's resize(..., LANCZOS), a new image
int image_resize(lua_State *L)
{
auto const &image{check_image(L, 1)};
auto const width{check_int(L, 2)};
auto const height{check_int(L, 3)};
luaL_argcheck(L, valid_image_side(width), 2, "between 1 and 4096");
luaL_argcheck(L, valid_image_side(height), 3, "between 1 and 4096");
std::vector<std::uint8_t> pixels(image_size(width, height));
resize_lanczos(image.pixels(), image.width(), image.height(), pixels, width, height);
push_object(L, canvas{width, height, std::move(pixels)}, image_metatable);
return 1;
}
int image_copy(lua_State *L)
{
auto const &image{check_image(L, 1)};
push_object(L, canvas{image}, image_metatable);
return 1;
}
// eu07.font and eu07.sprite methods ----------------------------------------------------------------------------------
int font_height(lua_State *L)
{
lua_pushinteger(L, check_object<bitmap_font>(L, 1, font_metatable).height());
return 1;
}
int font_text_width(lua_State *L)
{
auto const &font{check_object<bitmap_font>(L, 1, font_metatable)};
auto const text{check_text(L, 2)};
lua_pushinteger(L, font.text_width(text, optional_int(L, 3, 1)));
return 1;
}
int sprite_width(lua_State *L)
{
lua_pushinteger(L, check_object<glyph>(L, 1, sprite_metatable).width());
return 1;
}
int sprite_height(lua_State *L)
{
lua_pushinteger(L, check_object<glyph>(L, 1, sprite_metatable).height());
return 1;
}
constexpr std::array image_methods{
luaL_Reg{"width", image_width},
luaL_Reg{"height", image_height},
luaL_Reg{"fill", image_fill},
luaL_Reg{"set", image_set},
luaL_Reg{"get", image_get},
luaL_Reg{"fill_rect", image_fill_rect},
luaL_Reg{"draw_text", image_draw_text},
luaL_Reg{"draw_text_right", image_draw_text_right},
luaL_Reg{"draw_text_centered", image_draw_text_centered},
luaL_Reg{"draw_sprite", image_draw_sprite},
luaL_Reg{"draw_image", image_draw_image},
luaL_Reg{"led_post", image_led_post},
luaL_Reg{"resize", image_resize},
luaL_Reg{"copy", image_copy},
luaL_Reg{nullptr, nullptr},
};
constexpr std::array font_methods{
luaL_Reg{"height", font_height},
luaL_Reg{"text_width", font_text_width},
luaL_Reg{nullptr, nullptr},
};
constexpr std::array sprite_methods{
luaL_Reg{"width", sprite_width},
luaL_Reg{"height", sprite_height},
luaL_Reg{nullptr, nullptr},
};
constexpr std::array api_functions{
luaL_Reg{"image", api_image},
luaL_Reg{"font", api_font},
luaL_Reg{"sprite", api_sprite},
luaL_Reg{"time", api_time},
luaL_Reg{"log", api_log},
luaL_Reg{"command", api_command},
luaL_Reg{"read_file", api_read_file},
luaL_Reg{"include", api_include},
luaL_Reg{nullptr, nullptr},
};
template <std::size_t Size_> void make_metatable(lua_State *L, char const *Name, std::array<luaL_Reg, Size_> const &Methods, lua_CFunction Collect)
{
luaL_newmetatable(L, Name);
lua_newtable(L);
for (auto const &method : Methods)
{
if (method.name == nullptr)
{
break;
}
lua_pushcfunction(L, method.func);
lua_setfield(L, -2, method.name);
}
lua_setfield(L, -2, "__index");
lua_pushcfunction(L, Collect);
lua_setfield(L, -2, "__gc");
lua_pop(L, 1);
}
// the state of a refresh as a table, as the python screens get it in a dictionary; a list of points (touches) is a
// list of { x, y }
void push_state(lua_State *L, dictionary_source const &Input)
{
lua_createtable(L, 0, static_cast<int>(Input.floats.size() + Input.integers.size() + Input.bools.size() + Input.strings.size() + Input.vec2_lists.size()));
for (auto const &[key, value] : Input.floats)
{
lua_pushnumber(L, value);
lua_setfield(L, -2, key.c_str());
}
for (auto const &[key, value] : Input.integers)
{
lua_pushnumber(L, value);
lua_setfield(L, -2, key.c_str());
}
for (auto const &[key, value] : Input.bools)
{
lua_pushboolean(L, value ? 1 : 0);
lua_setfield(L, -2, key.c_str());
}
for (auto const &[key, value] : Input.strings)
{
auto const text{utf8_text(value)};
lua_pushlstring(L, text.data(), text.size());
lua_setfield(L, -2, key.c_str());
}
for (auto const &[key, points] : Input.vec2_lists)
{
lua_createtable(L, static_cast<int>(points.size()), 0);
for (std::size_t idx = 0; idx < points.size(); ++idx)
{
lua_createtable(L, 2, 0);
lua_pushnumber(L, points[idx].x);
lua_rawseti(L, -2, 1);
lua_pushnumber(L, points[idx].y);
lua_rawseti(L, -2, 2);
lua_rawseti(L, -2, static_cast<int>(idx + 1));
}
lua_setfield(L, -2, key.c_str());
}
}
void open_library(lua_State *L, lua_CFunction Open, char const *Name)
{
lua_pushcfunction(L, Open);
lua_pushstring(L, Name);
lua_call(L, 1, 0);
}
void lua_program::open_libraries()
{
open_library(m_state, luaopen_base, "");
open_library(m_state, luaopen_table, LUA_TABLIBNAME);
open_library(m_state, luaopen_string, LUA_STRLIBNAME);
open_library(m_state, luaopen_math, LUA_MATHLIBNAME);
open_library(m_state, luaopen_bit, LUA_BITLIBNAME);
// what reaches files or loads code
for (auto const *name : {"dofile", "loadfile", "load", "loadstring", "module", "require"})
{
lua_pushnil(m_state);
lua_setglobal(m_state, name);
}
lua_getglobal(m_state, LUA_STRLIBNAME);
lua_pushnil(m_state);
lua_setfield(m_state, -2, "dump");
lua_pop(m_state, 1);
}
void lua_program::register_api()
{
auto *L{m_state};
lua_pushlightuserdata(L, const_cast<char *>(&program_key)); // NOSONAR: the address is only a key
lua_pushlightuserdata(L, this);
lua_rawset(L, LUA_REGISTRYINDEX);
make_metatable(L, image_metatable, image_methods, collect<canvas>);
make_metatable(L, font_metatable, font_methods, collect<bitmap_font>);
make_metatable(L, sprite_metatable, sprite_methods, collect<glyph>);
lua_newtable(L);
for (auto const &function : api_functions)
{
if (function.name == nullptr)
{
break;
}
lua_pushcfunction(L, function.func);
lua_setfield(L, -2, function.name);
}
lua_setglobal(L, "eu07");
// print() writes to the log
lua_pushcfunction(L, api_log);
lua_setglobal(L, "print");
}
bool lua_program::run(int const Arguments, int const Results, std::string_view const What)
{
budget = instruction_limit;
if (0 == lua_pcall(m_state, Arguments, Results, 0))
{
return true;
}
auto const *message{lua_tostring(m_state, -1)};
ErrorLog(std::format("Lua screen \"{}\": {} failed: {}", script, What, message != nullptr ? message : "unknown error"));
lua_pop(m_state, 1);
return false;
}
bool lua_program::start()
{
m_state = luaL_newstate();
if (m_state == nullptr)
{
ErrorLog(std::format("Lua screen \"{}\": failed to create the state", script));
return false;
}
// no compiled code: LuaJIT calls no hooks in it, and the hook is what stops a script stuck in a loop
luaJIT_setmode(m_state, 0, LUAJIT_MODE_ENGINE | LUAJIT_MODE_OFF);
open_libraries();
register_api();
lua_sethook(m_state, count_hook, LUA_MASKCOUNT, hook_interval);
std::ifstream file{script, std::ios::binary};
if (false == file.is_open())
{
ErrorLog(std::format("Lua screen \"{}\": file not found", script));
return false;
}
std::string const code{std::istreambuf_iterator<char>{file}, std::istreambuf_iterator<char>{}};
if (false == load_chunk(m_state, code, script))
{
ErrorLog(std::format("Lua screen \"{}\": {}", script, lua_tostring(m_state, -1)));
return false;
}
if (false == run(0, 1, "the script"))
{
return false;
}
if (false == lua_istable(m_state, -1))
{
ErrorLog(std::format("Lua screen \"{}\": the script has to return a table with a render function", script));
return false;
}
lua_getfield(m_state, -1, "render");
auto const renders{lua_isfunction(m_state, -1)};
lua_pop(m_state, 1);
if (false == renders)
{
ErrorLog(std::format("Lua screen \"{}\": the script has to return a table with a render function", script));
return false;
}
m_screen = luaL_ref(m_state, LUA_REGISTRYINDEX);
lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_screen);
lua_getfield(m_state, -1, "init");
lua_remove(m_state, -2);
if (lua_isfunction(m_state, -1))
{
if (false == run(0, 0, "init"))
{
return false;
}
}
else
{
lua_pop(m_state, 1);
}
WriteLog(std::format("Lua screen \"{}\": ready", script));
return true;
}
bool lua_program::render(render_call &Call)
{
call = &Call;
lua_rawgeti(m_state, LUA_REGISTRYINDEX, m_screen);
lua_getfield(m_state, -1, "render");
lua_remove(m_state, -2);
push_state(m_state, Call.input);
auto const rendered{run(1, 1, "render")};
call = nullptr;
if (false == rendered)
{
return false;
}
// nil: the screen didn't change, keep the image there is
auto succeeded{true};
if (auto const *image{test_object<canvas>(m_state, -1, image_metatable)}; image != nullptr)
{
Call.pixels = image->pixels();
Call.width = image->width();
Call.height = image->height();
}
else if (false == lua_isnil(m_state, -1))
{
ErrorLog(std::format("Lua screen \"{}\": render has to return an image or nil", script));
succeeded = false;
}
lua_pop(m_state, 1);
// the images the refreshes left behind
lua_gc(m_state, LUA_GCSTEP, 0);
return succeeded;
}
class lua_runtime_host final : public runtime
{
public:
std::unique_ptr<program> load(std::string const &Script, script_screen_kind const Kind) override
{
auto loaded{std::make_unique<lua_program>(Script, Kind)};
if (false == loaded->start())
{
return nullptr;
}
return loaded;
}
};
} // namespace
std::unique_ptr<runtime> make_lua_runtime()
{
return std::make_unique<lua_runtime_host>();
}
} // namespace script_screens