Merge pull request #843 from MihailRis/common-vc-funcs

new functions and settings
This commit is contained in:
MihailRis 2026-06-01 00:27:11 +03:00 • committed by GitHub
commit 025e32eec1
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
33 changed files with 617 additions and 421 deletions

View file

@ -190,18 +190,34 @@ void audio::initialize(
audio::get_channel(channel.name)->setVolume(value * value);
}, true));
}
objects_keeper.keepAlive(settings.acousticEffects.observe([=](auto value) {
objects_keeper.keepAlive(settings.acousticEffects.observe([=](bool value) {
if (value) return;
backend->setAcoustics(audio::Acoustics {});
}, true));
if (inputEnabled) {
::input_device = backend->openInputDevice("", 44100, 1, 16);
::input_enabled = true;
}
if (::input_device) {
::input_device->startCapture();
}
::input_enabled = inputEnabled;
objects_keeper.keepAlive(settings.recordingEnabled.observe([&settings](bool enabled) {
if (!::input_enabled) {
return;
}
if (!enabled && ::input_device == nullptr) {
return;
}
if (enabled) {
logger.info() << "recording enabled; input device is "
<< settings.inputDevice.get();
::input_device = backend->openInputDevice(
settings.inputDevice.get(), 44100, 1, 16
);
::input_device->startCapture();
} else {
if (::input_device) {
::input_device->stopCapture();
}
::input_device = nullptr;
}
}, true));
}
InputDevice* audio::get_input_device() {

View file

@ -2,6 +2,7 @@
#include <string>
#include <filesystem>
#include <unordered_map>
struct CoreParameters {
bool headless = false;
@ -13,4 +14,5 @@ struct CoreParameters {
std::filesystem::path projectFolder;
std::string debugServerString;
int tps = 20;
std::unordered_map<std::string, std::string> projectArgs;
};

View file

@ -189,7 +189,7 @@ void Engine::initialize(CoreParameters coreParameters) {
keepAlive(settings.audio.inputDevice.observe([](auto name) {
audio::set_input_device(name == "auto" ? "" : name);
}, true));
}));
project->loadProjectStartScript();
if (!params.headless) {

View file

@ -61,16 +61,19 @@ std::unique_ptr<UiDocument> UiDocument::read(
const scriptenv& penv,
const std::string& name,
const io::path& file,
const std::string& fileName
const std::string& fileName,
scriptenv&& env
) {
const std::string text = io::read_string(file);
auto xmldoc = xml::parse(file.string(), text);
auto env = penv == nullptr
? scripting::create_doc_environment(scripting::get_root_environment(), name)
: scripting::create_doc_environment(penv, name);
if (env == nullptr) {
env = penv == nullptr
? scripting::create_doc_environment(scripting::get_root_environment(), name)
: scripting::create_doc_environment(penv, name);
}
gui::UiXmlReader reader(gui, scriptenv(env));
gui::UiXmlReader reader(gui, env);
auto view = reader.readXML(file.string(), *xmldoc->getRoot());
view->setId("root");
UiDocScript script {};
@ -82,9 +85,3 @@ std::unique_ptr<UiDocument> UiDocument::read(
}
return std::make_unique<UiDocument>(name, script, view, env);
}
std::shared_ptr<gui::UINode> UiDocument::readElement(
gui::GUI& gui, const io::path& file, const std::string& fileName
) {
return read(gui, nullptr, file.name(), file, fileName)->getRoot();
}

View file

@ -50,9 +50,7 @@ public:
const scriptenv& parent_env,
const std::string& name,
const io::path& file,
const std::string& fileName
);
static std::shared_ptr<gui::UINode> readElement(
gui::GUI&, const io::path& file, const std::string& fileName
const std::string& fileName,
scriptenv&& env = nullptr
);
};

View file

@ -57,6 +57,10 @@ FullCheckBox::FullCheckBox(
add(checkbox);
auto& checkboxActions = actions;
label->setMargin(glm::vec4(5.f, 5.f, 0.f, 0.f));
label->listenAction(UIAction::CLICK, [&checkboxActions](auto& gui) {
checkboxActions.notify(UIAction::CLICK, gui);
});
add(label);
}

View file

@ -889,8 +889,8 @@ static std::shared_ptr<UINode> read_iframe(
return iframe;
}
UiXmlReader::UiXmlReader(gui::GUI& gui, scriptenv&& env)
: gui(gui), env(std::move(env)) {
UiXmlReader::UiXmlReader(gui::GUI& gui, const scriptenv& env)
: gui(gui), env(env) {
contextStack.emplace("");
add("image", read_image);
add("canvas", read_canvas);
@ -922,8 +922,7 @@ void UiXmlReader::addIgnore(const std::string& tag) {
ignored.insert(tag);
}
std::shared_ptr<UINode> UiXmlReader::readUINode(const xml::xmlelement& element
) {
std::shared_ptr<UINode> UiXmlReader::readUINode(const xml::xmlelement& element) {
if (element.has("if")) {
const auto& cond = element.attr("if").getText();
if (cond.empty() || cond == "false" || cond == "nil") return nullptr;

View file

@ -20,9 +20,9 @@ namespace gui {
std::unordered_set<std::string> ignored;
std::stack<std::string> contextStack;
std::string filename;
scriptenv env;
const scriptenv& env;
public:
UiXmlReader(gui::GUI& gui, scriptenv&& env);
UiXmlReader(gui::GUI& gui, const scriptenv& env);
void add(const std::string& tag, uinode_reader reader);
bool hasReader(const std::string& tag) const;

View file

@ -38,6 +38,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.addSection("audio");
builder.add("enabled", &settings.audio.enabled, false);
builder.add("recording-enabled", &settings.audio.recordingEnabled);
builder.add("volume-master", &settings.audio.volumeMaster);
builder.add("volume-regular", &settings.audio.volumeRegular);
builder.add("volume-ui", &settings.audio.volumeUI);
@ -101,6 +102,7 @@ SettingsHandler::SettingsHandler(EngineSettings& settings) {
builder.addSection("system");
builder.add("max-bg-asset-loaders", &settings.system.maxBgAssetLoaders);
builder.add("preserve-assets-during-frame", &settings.system.preserveAssetsDuringFrame);
builder.add("direct-scripting-data-access", &settings.system.directScriptingDataAccess);
}
dv::value SettingsHandler::getValue(const std::string& name) const {

View file

@ -1116,28 +1116,34 @@ static int l_gui_load_document(lua::State* L) {
auto args = lua::tovalue(L, 3);
auto prefix = filename.entryPoint();
auto env = scripting::get_root_environment();
auto parentEnv = scripting::get_root_environment();
if (content) {
if (auto runtime = content->getPackRuntime(prefix)) {
env = runtime->getEnvironment();
parentEnv = runtime->getEnvironment();
}
}
auto documentPtr = UiDocument::read(
engine->getGUI(), std::move(env), alias, filename, filename.string()
);
auto document = documentPtr.get();
engine->requireAssets().store(std::move(documentPtr), alias);
auto env = scripting::create_doc_environment(parentEnv, alias);
// namespace extension
if (lua::istable(L, 4)) {
if (lua::get_from(L, "table", "merge")) {
lua::pushenv(L, *document->getEnvironment());
if (lua::get_from(L, "table", "extend")) {
lua::pushenv(L, *env);
lua::pushvalue(L, 4);
lua::call(L, 2, 0);
lua::pop(L);
}
}
auto documentPtr = UiDocument::read(
engine->getGUI(),
nullptr,
alias,
filename,
filename.string(),
std::move(env)
);
auto document = documentPtr.get();
engine->requireAssets().store(std::move(documentPtr), alias);
scripting::on_ui_open(document, {args});
return 0;
}

View file

@ -17,6 +17,11 @@
#error LuaJIT required
#endif
#ifndef LUA_TCDATA
// unsafe, use for filtering only
#define LUA_TCDATA 10
#endif
namespace lua {
class luaerror : public std::runtime_error {
public:

View file

@ -15,8 +15,12 @@
#include "usertypes/lua_type_pcmstream.hpp"
#include "engine/Engine.hpp"
static debug::Logger logger("lua-state");
static lua::State* main_thread = nullptr;
namespace {
debug::Logger logger("lua-state");
lua::State* main_thread = nullptr;
bool headless_mode = false;
const std::unordered_map<std::string, std::string>* project_args;
}
using namespace lua;
@ -146,20 +150,53 @@ void lua::init_state(State* L, StateType stateType) {
newusertype<LuaHeightmap>(L);
newusertype<LuaVoxelFragment>(L);
newusertype<LuaCanvas>(L);
pushboolean(L, headless_mode);
setglobal(L, "__VC_HEADLESS");
createtable(L, 0, project_args->size());
for (const auto& [key, value] : *project_args) {
pushstring(L, value);
setfield(L, key);
}
setglobal(L, "__VC_PROJECT_ARGS");
auto file = "res:scripts/stdmin.lua";
auto src = io::read_string(file);
lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua"));
newusertype<LuaRandom>(L);
if (getglobal(L, "random")) {
if (getglobal(L, "__vc_Random")) {
setfield(L, "Random");
}
pop(L);
}
newusertype<LuaPCMStream>(L);
if (getglobal(L, "audio")) {
if (getglobal(L, "__vc_PCMStream")) {
setfield(L, "PCMStream");
}
pop(L);
}
if (stateType == StateType::GENERATOR) {
pushnil(L);
setglobal(L, "ffi");
}
}
void lua::initialize(const EnginePaths& paths, const CoreParameters& params) {
logger.info() << LUA_VERSION;
logger.info() << LUAJIT_VERSION;
headless_mode = params.headless;
project_args = &params.projectArgs;
main_thread = create_state(
paths, params.headless ? StateType::SCRIPT : StateType::BASE
);
lua::pushstring(main_thread, params.scriptFile.stem().u8string());
lua::setglobal(main_thread, "__VC_SCRIPT_NAME");
lua::pushboolean(main_thread, params.headless);
lua::setglobal(main_thread, "__VC_HEADLESS");
}
void lua::finalize() {
@ -191,29 +228,5 @@ State* lua::create_state(const EnginePaths& paths, StateType stateType) {
throw luaerror("could not initialize Lua state");
}
init_state(L, stateType);
auto file = "res:scripts/stdmin.lua";
auto src = io::read_string(file);
lua::pop(L, lua::execute(L, 0, src, "core:scripts/stdmin.lua"));
newusertype<LuaRandom>(L);
if (getglobal(L, "random")) {
if (getglobal(L, "__vc_Random")) {
setfield(L, "Random");
}
pop(L);
}
newusertype<LuaPCMStream>(L);
if (getglobal(L, "audio")) {
if (getglobal(L, "__vc_PCMStream")) {
setfield(L, "PCMStream");
}
pop(L);
}
if (stateType == StateType::GENERATOR) {
pushnil(L);
setglobal(L, "ffi");
}
return L;
}

View file

@ -5,6 +5,8 @@
#include <iostream>
#include "util/stringutil.hpp"
#include "engine/Engine.hpp"
#include "debug/Logger.hpp"
using namespace lua;
@ -12,6 +14,8 @@ static int next_environment = 1;
std::unordered_map<std::type_index, std::string> lua::usertypeNames;
static debug::Logger logger("lua-util");
int lua::userdata_destructor(lua::State* L) {
if (auto obj = touserdata<Userdata>(L, 1)) {
obj->~Userdata();
@ -369,3 +373,55 @@ void lua::remove_environment(State* L, int id) {
pushnil(L);
store_env(L, id);
}
static inline std::string_view bytearray_as_string_indirect(lua::State* L, int idx) {
requireglobal(L, "Bytearray_as_string");
pushvalue(L, -2);
call(L, 1, 1);
auto view = tolstring(L, -1);
pop(L, 2);
return view;
}
std::string_view lua::bytearray_as_string(lua::State* L, int idx) {
const auto& settings = scripting::engine->getSettings();
int luaType = type(L, idx);
if (luaType == LUA_TSTRING) {
return tolstring(L, idx);
} else if (luaType == LUA_TTABLE) {
return bytearray_as_string_indirect(L, idx);
}
pushvalue(L, idx);
if (settings.system.directScriptingDataAccess.get()) {
requireglobal(L, "Bytearray_as_ptr");
pushvalue(L, -2);
call(L, 1, 2);
auto view = tolstring(L, -2);
if (view == "0") {
if (luaType == LUA_TCDATA) {
logger.error() << "FFI-based Bytearray_as_ptr returned null-pointer";
}
pop(L, 2);
return bytearray_as_string_indirect(L, idx);
}
uint64_t size = touinteger(L, -1);
auto ptr = (const char*)std::stoull(std::string(view), nullptr, 16);
pop(L, 3);
return std::string_view(ptr, size);
} else {
return bytearray_as_string_indirect(L, idx);
}
}
void lua::loadbuffer(
lua::State* L, int env, const std::string& src, const std::string& file
) {
if (luaL_loadbuffer(L, src.c_str(), src.length(), file.c_str())) {
throw luaerror(tostring(L, -1));
}
if (env && getregistry(L, ENVS_TABLE, env_name(env))) {
lua_setfenv(L, -2);
}
}

View file

@ -555,16 +555,9 @@ namespace lua {
return lua_setfenv(L, idx);
}
inline void loadbuffer(
void loadbuffer(
lua::State* L, int env, const std::string& src, const std::string& file
) {
if (luaL_loadbuffer(L, src.c_str(), src.length(), file.c_str())) {
throw luaerror(tostring(L, -1));
}
if (env && getregistry(L, ENVS_TABLE, env_name(env))) {
lua_setfenv(L, -2);
}
}
);
inline void store_in(
lua::State* L, const std::string& tableName, const std::string& name
@ -828,16 +821,5 @@ namespace lua {
return create_bytearray(L, bytes.data(), bytes.size());
}
inline std::string_view bytearray_as_string(lua::State* L, int idx) {
if (type(L, idx) == LUA_TSTRING) {
return tolstring(L, idx);
}
pushvalue(L, idx);
requireglobal(L, "Bytearray_as_string");
pushvalue(L, -2);
call(L, 1, 1);
auto view = tolstring(L, -1);
pop(L, 2);
return view;
}
std::string_view bytearray_as_string(lua::State* L, int idx);
}

View file

@ -141,7 +141,7 @@ std::unique_ptr<IClientProjectScript> scripting::load_client_project_script(
auto source = io::read_string(script);
auto env = create_environment(nullptr);
lua::pushenv(L, *env);
if (lua::getglobal(L, "__vc_app")) {
if (lua::getregistry(L, "app")) {
lua::setfield(L, "app");
}
lua::pop(L);

View file

@ -10,6 +10,7 @@
struct AudioSettings {
/// @brief try to initialize AL backend
FlagSetting enabled {true};
FlagSetting recordingEnabled {false};
NumberSetting volumeMaster {1.0f, 0.0f, 1.0f, setting_format::percent};
NumberSetting volumeRegular {1.0f, 0.0f, 1.0f, setting_format::percent};
@ -126,6 +127,8 @@ struct SystemSettings {
IntegerSetting maxBgAssetLoaders {3, -4, 16};
/// @brief Preserve assets during current frame from being destroyed
FlagSetting preserveAssetsDuringFrame {true};
/// @brief Allow luajit cdata objects direct data access optimizations
FlagSetting directScriptingDataAccess {true};
};
struct EngineSettings {

View file

@ -26,6 +26,13 @@ namespace util {
return last[0] == '-';
}
bool isNextKeywordArg() const {
if (pos >= argc) {
throw std::runtime_error("unexpected end");
}
return argv[pos][0] == '-';
}
std::string next() {
if (pos >= argc) {
throw std::runtime_error("unexpected end");

View file

@ -97,11 +97,32 @@ static bool perform_keyword(
throw std::runtime_error("unknown argument " + keyword);
}
static void parse_project_args(util::ArgsReader& reader, CoreParameters& params) {
while (reader.hasNext()) {
std::string key = reader.next();
if (!reader.isKeywordArg()) {
std::cerr << "--<keyword> argument expected" << std::endl;
return;
}
key = key.substr(2);
if (!reader.hasNext() || reader.isNextKeywordArg()) {
params.projectArgs[std::move(key)] = "";
continue;
}
std::string value = reader.next();
params.projectArgs[std::move(key)] = std::move(value);
}
}
bool parse_cmdline(int argc, char** argv, CoreParameters& params) {
util::ArgsReader reader(argc, argv);
reader.skip();
while (reader.hasNext()) {
std::string token = reader.next();
if (token == "--") {
parse_project_args(reader, params);
return true;
}
if (reader.isKeywordArg()) {
if (!perform_keyword(reader, token, params)) {
return false;