Merge remote-tracking branch 'upstream/client-side-tests' into io-streams-enhance

# Conflicts:
#	src/logic/scripting/descriptors_manager.cpp
#	src/logic/scripting/descriptors_manager.hpp
This commit is contained in:
Onran 2026-06-21 23:55:08 +09:00
commit db014cca09
46 changed files with 1172 additions and 282 deletions

View file

@ -0,0 +1,133 @@
#include "logic/scripting/io_descriptors.hpp"
#include "debug/Logger.hpp"
#include "io/io.hpp"
#include <memory>
#include <string>
#include <vector>
#include <optional>
static debug::Logger logger("descriptors-manager");
using namespace scripting;
namespace {
struct StreamDescriptor {
// TODO: std::iostream?
std::unique_ptr<std::istream> in;
std::unique_ptr<std::ostream> out;
};
std::vector<std::optional<StreamDescriptor>> descriptors;
}
std::istream* io_descriptors::get_input(int id) {
if (!is_readable(id)) {
return nullptr;
}
return ::descriptors[id]->in.get();
}
std::ostream* io_descriptors::get_output(int id) {
if (!is_writeable(id)) {
return nullptr;
}
return ::descriptors[id]->out.get();
}
static StreamDescriptor& require_descriptor(int id) {
if (!io_descriptors::has_descriptor(id)) {
throw std::runtime_error(
"io-descriptor with id " + std::to_string(id) + " does not exists"
);
}
return *::descriptors[id];
}
std::istream& io_descriptors::require_input(int id) {
const auto& descriptor = require_descriptor(id);
if (descriptor.in) {
return *descriptor.in;
}
throw std::runtime_error("io-descriptor is not readable");
}
std::ostream& io_descriptors::require_output(int id) {
const auto& descriptor = require_descriptor(id);
if (descriptor.out) {
return *descriptor.out;
}
throw std::runtime_error("io-descriptor is not writeable");
}
void io_descriptors::flush(int id) {
if (is_writeable(id)) {
::descriptors[id]->out->flush();
}
}
bool io_descriptors::has_descriptor(int id) {
return id >= 0 && id < static_cast<int>(::descriptors.size()) &&
::descriptors[id].has_value();
}
bool io_descriptors::is_readable(int id) {
return has_descriptor(id) && ::descriptors[id]->in != nullptr;
}
bool io_descriptors::is_writeable(int id) {
return id >= 0 && id < static_cast<int>(::descriptors.size())
&& ::descriptors[id].has_value()
&& ::descriptors[id]->out != nullptr;
}
void io_descriptors::close(int id) {
if (!has_descriptor(id)) {
return;
}
auto& desc = ::descriptors[id].value();
if (desc.out) {
desc.out->flush();
}
desc.in.reset();
desc.out.reset();
::descriptors[id] = std::nullopt;
}
int io_descriptors::open_descriptor(const io::path& path, bool write, bool read) {
std::unique_ptr<std::istream> in;
std::unique_ptr<std::ostream> out;
try {
if (read) {
in = io::read(path);
}
if (write) {
out = io::write(path);
}
} catch (const std::exception& e) {
logger.error() << "failed to open descriptor for " << path.string()
<< ": " << e.what();
return -1;
}
for (int i = 0; i < static_cast<int>(descriptors.size()); ++i) {
if (!descriptors[i].has_value()) {
descriptors[i] = StreamDescriptor{ std::move(in), std::move(out) };
return i;
}
}
::descriptors.emplace_back(StreamDescriptor{ std::move(in), std::move(out) });
return static_cast<int>(::descriptors.size() - 1);
}
void io_descriptors::close_all_descriptors() {
for (int i = 0; i < static_cast<int>(::descriptors.size()); ++i) {
if (::descriptors[i].has_value()) {
close(i);
}
}
::descriptors.clear();
}

View file

@ -0,0 +1,26 @@
#pragma once
#include <istream>
#include <ostream>
#include "io/fwd.hpp"
namespace scripting::io_descriptors {
std::istream* get_input(int id);
std::ostream* get_output(int id);
std::istream& require_input(int id);
std::ostream& require_output(int id);
void flush(int id);
bool has_descriptor(int id);
bool is_readable(int id);
bool is_writeable(int id);
void close(int id);
int open_descriptor(const io::path& path, bool write, bool read);
void close_all_descriptors();
}

View file

@ -44,6 +44,7 @@ extern const luaL_Reg posteffectslib[]; // gfx.posteffects
extern const luaL_Reg quatlib[];
extern const luaL_Reg randomlib[];
extern const luaL_Reg compressionlib[];
extern const luaL_Reg testlib[];
extern const luaL_Reg text3dlib[]; // gfx.text3d
extern const luaL_Reg timelib[];
extern const luaL_Reg tomllib[];

View file

@ -10,7 +10,7 @@
#include "util/stringutil.hpp"
#include "api_lua.hpp"
#include "../lua_engine.hpp"
#include "logic/scripting/descriptors_manager.hpp"
#include "logic/scripting/io_descriptors.hpp"
namespace fs = std::filesystem;
using namespace scripting;
@ -263,13 +263,13 @@ static int l_open_descriptor(lua::State* L) {
std::vector<char> buffer;
if(wplusMode) {
int temp_descriptor = scripting::descriptors_manager::open_descriptor(path, false, true);
int temp_descriptor = io_descriptors::open_descriptor(path, false, true);
if (temp_descriptor == -1) {
throw std::runtime_error("failed to open descriptor for initial reading");
}
auto* in_stream = scripting::descriptors_manager::get_input(temp_descriptor);
auto* in_stream = io_descriptors::get_input(temp_descriptor);
in_stream->seekg(0, std::ios::end);
std::streamsize size = in_stream->tellg();
@ -278,17 +278,17 @@ static int l_open_descriptor(lua::State* L) {
buffer.resize(size);
in_stream->read(buffer.data(), size);
scripting::descriptors_manager::close(temp_descriptor);
io_descriptors::close(temp_descriptor);
}
int descriptor = scripting::descriptors_manager::open_descriptor(path, write, read);
int descriptor = io_descriptors::open_descriptor(path, write, read);
if(descriptor == -1) {
throw std::runtime_error("failed to open descriptor");
}
if(wplusMode) {
auto* out_stream = scripting::descriptors_manager::get_output(descriptor);
auto* out_stream = io_descriptors::get_output(descriptor);
out_stream->write(buffer.data(), buffer.size());
out_stream->flush();
}
@ -297,51 +297,31 @@ static int l_open_descriptor(lua::State* L) {
}
static int l_has_descriptor(lua::State* L) {
return lua::pushboolean(L, scripting::descriptors_manager::has_descriptor(lua::tointeger(L, 1)));
return lua::pushboolean(L, io_descriptors::has_descriptor(lua::tointeger(L, 1)));
}
static int l_read_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
if (!scripting::descriptors_manager::is_readable(descriptor)) {
throw std::runtime_error("descriptor is not readable");
}
int maxlen = lua::tointeger(L, 2);
auto* stream = scripting::descriptors_manager::get_input(descriptor);
auto& stream = io_descriptors::require_input(descriptor);
if (stream.eof()) {
stream.clear();
}
util::Buffer<char> buffer(maxlen);
stream->read(buffer.data(), maxlen);
std::streamsize read_len = stream->gcount();
stream.read(buffer.data(), maxlen);
std::streamsize read_len = stream.gcount();
return lua::create_bytearray(L, buffer.data(), read_len);
}
static int l_write_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
if (!scripting::descriptors_manager::is_writeable(descriptor)) {
throw std::runtime_error("descriptor is not writeable");
}
auto data = lua::bytearray_as_string(L, 2);
auto* stream = scripting::descriptors_manager::get_output(descriptor);
stream->write(data.data(), static_cast<std::streamsize>(data.size()));
if (!stream->good()) {
auto& stream = io_descriptors::require_output(descriptor);
stream.write(data.data(), static_cast<std::streamsize>(data.size()));
if (!stream.good()) {
throw std::runtime_error("failed to write to stream");
}
return 0;
@ -350,12 +330,13 @@ static int l_write_descriptor(lua::State* L) {
static int l_seek_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
std::string mode = lua::require_string(L, 2);
auto mode = lua::require_string(L, 2);
std::ios_base::seekdir dir;
auto position = lua::tointeger(L, 3);
switch (mode[0]) {
case 'b':
@ -371,29 +352,51 @@ static int l_seek_descriptor(lua::State* L) {
throw std::runtime_error("invalid seek mode");
}
auto* stream = scripting::descriptors_manager::get_output(descriptor);
if (io_descriptors::is_writeable(descriptor)) {
auto& stream = io_descriptors::require_output(descriptor);
stream.seekp(position, dir);
if (!stream.good()) {
throw std::runtime_error("failed to seek stream");
}
}
if (io_descriptors::is_readable(descriptor)) {
auto& stream = io_descriptors::require_input(descriptor);
stream.seekg(position, dir);
if (!stream.good()) {
throw std::runtime_error("failed to seek stream");
}
}
return 0;
}
stream->seekp(lua::tointeger(L, 3), dir);
static int l_tell_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!stream->good()) {
throw std::runtime_error("failed to seek stream");
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
return 0;
if (io_descriptors::is_writeable(descriptor)) {
auto& stream = io_descriptors::require_output(descriptor);
return lua::pushinteger(L, stream.tellp());
} else {
auto& stream = io_descriptors::require_input(descriptor);
return lua::pushinteger(L, stream.tellg());
}
}
static int l_flush_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
if (!scripting::descriptors_manager::is_writeable(descriptor)) {
if (!io_descriptors::is_writeable(descriptor)) {
throw std::runtime_error("descriptor is not writeable");
}
scripting::descriptors_manager::flush(descriptor);
io_descriptors::flush(descriptor);
return 0;
}
@ -414,16 +417,16 @@ static int l_available_descriptor(lua::State* L) {
static int l_close_descriptor(lua::State* L) {
int descriptor = lua::tointeger(L, 1);
if (!scripting::descriptors_manager::has_descriptor(descriptor)) {
if (!io_descriptors::has_descriptor(descriptor)) {
throw std::runtime_error("unknown descriptor");
}
scripting::descriptors_manager::close(descriptor);
io_descriptors::close(descriptor);
return 0;
}
static int l_close_all_descriptors(lua::State* L) {
scripting::descriptors_manager::close_all_descriptors();
io_descriptors::close_all_descriptors();
return 0;
}
@ -456,6 +459,7 @@ const luaL_Reg filelib[] = {
{"__read_descriptor", lua::wrap<l_read_descriptor>},
{"__write_descriptor", lua::wrap<l_write_descriptor>},
{"__seek_descriptor", lua::wrap<l_seek_descriptor>},
{"__tell_descriptor", lua::wrap<l_tell_descriptor>},
{"__flush_descriptor", lua::wrap<l_flush_descriptor>},
{"__available_descriptor", lua::wrap<l_available_descriptor>},
{"__close_descriptor", lua::wrap<l_close_descriptor>},

View file

@ -420,15 +420,19 @@ static int p_get_data(UINode* node, lua::State* L) {
return 0;
}
static const std::string& request_node_id(const DocumentNode& docnode) {
std::string id = docnode.node->getId();
static const std::string& request_node_id(UiDocument& document, UINode& node) {
const std::string& id = node.getId();
if (id.empty()) {
id = "#" + std::to_string(
reinterpret_cast<std::ptrdiff_t>(docnode.node.get()));
node.setId( "#" + std::to_string(
reinterpret_cast<std::ptrdiff_t>(&node)));
document.pushIndices(node.shared_from_this());
return node.getId();
}
docnode.node->setId(std::move(id));
docnode.document->pushIndices(docnode.node);
return docnode.node->getId();
return id;
}
static const std::string& request_node_id(const DocumentNode& docnode) {
return request_node_id(*docnode.document, *docnode.node);
}
/// @brief Push UI-document node object to stack
@ -443,15 +447,14 @@ static int push_document_node(lua::State* L, const std::string& id) {
static int p_get_parent(UINode* node, lua::State* L) {
auto parent = node->getParent();
if (!parent) {
if (parent == nullptr) {
return 0;
}
auto docname = lua::require_string(L, 1);
auto element = lua::require_string(L, 2);
auto docnode = get_document_node_impl(L, docname, element);
const auto& id = request_node_id(docnode);
const auto& id = request_node_id(*docnode.document, *parent);
return push_document_node(L, id);
}
@ -1144,7 +1147,7 @@ static int l_gui_load_document(lua::State* L) {
auto document = documentPtr.get();
engine->requireAssets().store(std::move(documentPtr), alias);
scripting::on_ui_open(document, {args});
scripting::on_ui_open(*document, {args});
return 0;
}

View file

@ -0,0 +1,45 @@
#include "api_lua.hpp"
#include "engine/Engine.hpp"
#include "window/input.hpp"
#include <iostream>
using namespace scripting;
static int l_set_button_pressed(lua::State* L) {
int button = lua::touinteger(L, 1);
int x = lua::tointeger(L, 2);
int y = lua::tointeger(L, 3);
bool pressed = lua::toboolean(L, 4);
auto& input = engine->getInput();
input.simulateCursorPos(x, y);
input.simulateClick(button, pressed);
return 0;
}
static int l_set_key_pressed(lua::State* L) {
auto key = lua::require_string(L, 1);
bool pressed = lua::toboolean(L, 2);
auto& input = engine->getInput();
input.simulateKey(input_util::keycode_from(key), pressed);
return 0;
}
static int l_enter_text(lua::State* L) {
auto string = lua::require_wstring(L, 1);
auto& input = engine->getInput();
for (auto chr : string) {
input.simulateCodepoint(chr);
}
return 0;
}
const luaL_Reg testlib[] = {
{"set_button_pressed", lua::wrap<l_set_button_pressed>},
{"set_key_pressed", lua::wrap<l_set_key_pressed>},
{"enter_text", lua::wrap<l_enter_text>},
{nullptr, nullptr}
};

View file

@ -129,6 +129,9 @@ static int l_get_generator(lua::State* L) {
}
static int l_get_chunk_data(lua::State* L) {
if (level == nullptr) {
throw std::runtime_error("world is not open");
}
int x = static_cast<int>(lua::tointeger(L, 1));
int z = static_cast<int>(lua::tointeger(L, 2));
const auto& chunk = level->chunks->getChunk(x, z);
@ -175,7 +178,7 @@ static void integrate_chunk_client(Chunk& chunk) {
static int l_set_chunk_data(lua::State* L) {
if (level == nullptr) {
throw std::runtime_error("no open world");
throw std::runtime_error("world is not open");
}
int x = static_cast<int>(lua::tointeger(L, 1));
@ -201,7 +204,7 @@ static int l_set_chunk_data(lua::State* L) {
static int l_save_chunk_data(lua::State* L) {
if (level == nullptr) {
throw std::runtime_error("no open world");
throw std::runtime_error("world is not open");
}
int x = static_cast<int>(lua::tointeger(L, 1));

View file

@ -19,6 +19,7 @@ namespace {
debug::Logger logger("lua-state");
lua::State* main_thread = nullptr;
bool headless_mode = false;
bool test_mode = false;
const std::unordered_map<std::string, std::string>* project_args;
}
@ -69,12 +70,12 @@ static void create_libs(State* L, StateType stateType) {
openlib(L, "yaml", yamllib);
openlib(L, "__vc_app", applib);
lua::getglobal(L, "__vc_app");
lua::setregistry(L, "app");
getglobal(L, "__vc_app");
setregistry(L, "app");
if (stateType == StateType::SCRIPT) {
lua::getregistry(L, "app");
lua::setglobal(L, "app");
getregistry(L, "app");
setglobal(L, "app");
}
if (stateType == StateType::BASE || stateType == StateType::SCRIPT) {
openlib(L, "assets", assetslib);
@ -99,6 +100,10 @@ static void create_libs(State* L, StateType stateType) {
openlib(L, "__transform", transformlib);
}
if (::test_mode) {
openlib(L, "test", testlib);
}
addfunc(L, "print", lua::wrap<l_print>);
addfunc(L, "crc32", lua::wrap<l_crc32>);
}
@ -191,6 +196,7 @@ void lua::initialize(const EnginePaths& paths, const CoreParameters& params) {
logger.info() << LUAJIT_VERSION;
headless_mode = params.headless;
test_mode = params.testMode;
project_args = &params.projectArgs;
main_thread = create_state(
paths, params.headless ? StateType::SCRIPT : StateType::BASE

View file

@ -591,36 +591,46 @@ bool scripting::on_item_break_block(
);
}
void scripting::on_ui_open(
UiDocument* layout, std::vector<dv::value> args
) {
auto argsptr =
std::make_shared<std::vector<dv::value>>(std::move(args));
std::string name = layout->getId() + ".open";
lua::emit_event(lua::get_main_state(), name, [=](auto L) {
for (const auto& value : *argsptr) {
lua::pushvalue(L, value);
static void call_layout_event(const UiDocument& layout, const std::vector<dv::value>& args, const std::string& eventName) {
auto L = lua::get_main_state();
lua::pushenv(L, *layout.getEnvironment());
if (lua::getfield(L, eventName)) {
for (const auto& arg : args) {
lua::pushvalue(L, arg);
}
return argsptr->size();
});
lua::call_nothrow(L, args.size(), 0);
}
lua::pop(L);
}
void scripting::on_ui_open(
const UiDocument& layout, std::vector<dv::value> args
) {
if (layout.getScript().onopen) {
call_layout_event(layout, args, "on_open");
}
}
void scripting::on_ui_progress(
UiDocument* layout, int workDone, int workTotal
const UiDocument& layout, int workDone, int workTotal
) {
std::string name = layout->getId() + ".progress";
lua::emit_event(lua::get_main_state(), name, [=](auto L) {
lua::pushinteger(L, workDone);
lua::pushinteger(L, workTotal);
return 2;
});
if (layout.getScript().onprogress){
call_layout_event(layout, {workDone, workTotal}, "on_progress");
}
}
void scripting::on_ui_close(UiDocument* layout, Inventory* inventory) {
std::string name = layout->getId() + ".close";
lua::emit_event(lua::get_main_state(), name, [inventory](auto L) {
return lua::pushinteger(L, inventory ? inventory->getId() : 0);
});
void scripting::on_ui_close(const UiDocument& layout, Inventory* inventory) {
if (layout.getScript().onclose) {
call_layout_event(
layout, {inventory ? inventory->getId() : 0}, "on_close"
);
}
}
void scripting::on_ui_destroy(const UiDocument& layout) {
if (layout.getScript().ondestroy) {
call_layout_event(layout, {}, "on_destroy");
}
}
void scripting::on_scripts_loading() {
@ -805,11 +815,14 @@ void scripting::load_layout_script(
) {
int env = *senv;
lua::pop(lua::get_main_state(), load_script(env, "layout", file, fileName));
script.onopen = register_event(env, "on_open", prefix + ".open");
script.onprogress =
register_event(env, "on_progress", prefix + ".progress");
script.onclose = register_event(env, "on_close", prefix + ".close");
auto L = lua::get_main_state();
lua::pop(L, load_script(env, "layout", file, fileName));
lua::pushenv(L, env);
script.onopen = lua::hasfield(L, "on_open");
script.onprogress = lua::hasfield(L, "on_progress");
script.onclose = lua::hasfield(L, "on_close");
script.ondestroy = lua::hasfield(L, "on_destroy");
lua::pop(L);
}
void scripting::close() {

View file

@ -152,13 +152,16 @@ namespace scripting {
void on_attacked(const Entity& entity, Player* player, entityid_t attacker);
void on_entity_used(const Entity& entity, Player* player);
/// @brief Called on UI view show
void on_ui_open(UiDocument* layout, std::vector<dv::value> args);
/// @brief Called on UI document show
void on_ui_open(const UiDocument& layout, std::vector<dv::value> args);
void on_ui_progress(UiDocument* layout, int workDone, int totalWork);
void on_ui_progress(const UiDocument& layout, int workDone, int totalWork);
/// @brief Called on UI view close
void on_ui_close(UiDocument* layout, Inventory* inventory);
/// @brief Called on UI document close
void on_ui_close(const UiDocument& layout, Inventory* inventory);
/// @brief Called on UI document destroy
void on_ui_destroy(const UiDocument& layout);
/// @brief Called on Content loading
void on_scripts_loading();