From fc3994446a2a21875bb5a58b0da0c8866da50ba8 Mon Sep 17 00:00:00 2001 From: MihailRis Date: Sat, 29 Jun 2024 21:01:30 +0300 Subject: [PATCH] update entity script semantics --- res/content/base/scripts/drop.lua | 11 +-- res/modules/internal/stdcomp.lua | 28 +++++-- src/content/ContentLoader.cpp | 2 +- src/logic/scripting/lua/lua_engine.cpp | 3 + src/logic/scripting/lua/lua_util.cpp | 8 +- src/logic/scripting/lua/lua_util.hpp | 53 +++++++++++- src/logic/scripting/scripting.cpp | 107 +++++++++++++++++++------ src/logic/scripting/scripting.hpp | 11 ++- src/objects/Entities.cpp | 14 ++-- src/objects/Entities.hpp | 15 ++++ src/objects/EntityDef.hpp | 10 +-- 11 files changed, 193 insertions(+), 69 deletions(-) diff --git a/res/content/base/scripts/drop.lua b/res/content/base/scripts/drop.lua index d059d07c..4b09fc3b 100644 --- a/res/content/base/scripts/drop.lua +++ b/res/content/base/scripts/drop.lua @@ -1,12 +1,7 @@ -function on_spawn(eid) - print("spawn", eid) +function on_despawn() + print("despawn") end -function on_despawn(eid) - print("despawn", eid) -end - -function on_grounded(eid) - local entity = stdcomp.new_Entity(eid) -- test +function on_grounded() entity.transform:set_rot(mat4.rotate({0, 1, 0}, math.random()*360)) end diff --git a/res/modules/internal/stdcomp.lua b/res/modules/internal/stdcomp.lua index d526c65f..fa1fc020 100644 --- a/res/modules/internal/stdcomp.lua +++ b/res/modules/internal/stdcomp.lua @@ -25,11 +25,25 @@ end -- Entity class -local Entity = {__index={}} +local Entity = {__index={ + despawn=function(self) return entity.despawn(self.eid) end, +}} -return {new_Entity = function(eid) - local entity = setmetatable({eid=eid}, Entity) - entity.transform = new_Transform(eid) - entity.rigidbody = new_Rigidbody(eid) - return entity -end} +local entities = {} + +return { + new_Entity = function(eid) + local entity = setmetatable({eid=eid}, Entity) + entity.transform = new_Transform(eid) + entity.rigidbody = new_Rigidbody(eid) + entities[eid] = entity; + return entity + end, + remove_Entity = function(eid) + local entity = entities[eid] + if entity and entity.on_despawn then + entity.on_despawn() + end + entities[eid] = nil; + end +} diff --git a/src/content/ContentLoader.cpp b/src/content/ContentLoader.cpp index ad4f7072..530d218a 100644 --- a/src/content/ContentLoader.cpp +++ b/src/content/ContentLoader.cpp @@ -324,7 +324,7 @@ void ContentLoader::loadEntity(EntityDef& def, const std::string& full, const st auto scriptfile = folder/fs::path("scripts/"+def.scriptName+".lua"); if (fs::is_regular_file(scriptfile)) { - scripting::load_entity_script(env, full, scriptfile, def.rt.funcsset); + scripting::load_entity_script(env, def, scriptfile); } } diff --git a/src/logic/scripting/lua/lua_engine.cpp b/src/logic/scripting/lua/lua_engine.cpp index 0ca13127..fab28a7b 100644 --- a/src/logic/scripting/lua/lua_engine.cpp +++ b/src/logic/scripting/lua/lua_engine.cpp @@ -91,6 +91,9 @@ void lua::initialize() { createtable(L, 0, 0); setglobal(L, LAMBDAS_TABLE); + createtable(L, 0, 0); + setglobal(L, CHUNKS_TABLE); + newusertype(L, "bytearray"); } diff --git a/src/logic/scripting/lua/lua_util.cpp b/src/logic/scripting/lua/lua_util.cpp index 745889d2..ca2c3c00 100644 --- a/src/logic/scripting/lua/lua_util.cpp +++ b/src/logic/scripting/lua/lua_util.cpp @@ -118,15 +118,15 @@ int lua::call(State* L, int argc, int nresults) { if (lua_pcall(L, argc, nresults, 0)) { throw luaerror(tostring(L, -1)); } - return 1; + return nresults == -1 ? 1 : nresults; } -int lua::call_nothrow(State* L, int argc) { +int lua::call_nothrow(State* L, int argc, int nresults) { if (lua_pcall(L, argc, LUA_MULTRET, 0)) { log_error(tostring(L, -1)); return 0; } - return 1; + return nresults == -1 ? 1 : nresults; } void lua::dump_stack(State* L) { @@ -198,7 +198,7 @@ scripting::common_func lua::create_lambda(State* L) { }; } -int lua::createEnvironment(State* L, int parent) { +int lua::create_environment(State* L, int parent) { int id = nextEnvironment++; // local env = {} diff --git a/src/logic/scripting/lua/lua_util.hpp b/src/logic/scripting/lua/lua_util.hpp index 9a9665a0..ed0b1cde 100644 --- a/src/logic/scripting/lua/lua_util.hpp +++ b/src/logic/scripting/lua/lua_util.hpp @@ -8,8 +8,10 @@ #include #include +// NOTE: const std::string& used instead of string_view because c_str() needed namespace lua { - inline std::string LAMBDAS_TABLE = "$L"; + inline std::string LAMBDAS_TABLE = "$L"; // lambdas storage + inline std::string CHUNKS_TABLE = "$C"; // precompiled lua chunks extern std::unordered_map usertypeNames; int userdata_destructor(lua::State* L); @@ -79,6 +81,14 @@ namespace lua { return true; } + inline int requireglobal(lua::State* L, const std::string& name) { + if (getglobal(L, name)) { + return 1; + } else { + throw std::runtime_error("global name "+name+" not found"); + } + } + inline bool hasglobal(lua::State* L, const std::string& name) { lua_getglobal(L, name.c_str()); if (isnil(L, -1)) { @@ -443,6 +453,16 @@ namespace lua { return true; } + inline bool hasfield(lua::State* L, const std::string& name, int idx=-1) { + lua_getfield(L, idx, name.c_str()); + if (isnil(L, -1)) { + pop(L); + return false; + } + pop(L); + return true; + } + inline const char* require_string(lua::State* L, int idx) { if (!isstring(L, idx)) { throw luaerror("string expected at "+std::to_string(idx)); @@ -471,6 +491,10 @@ namespace lua { setglobal(L, name); } + inline int setfenv(lua::State* L, int idx=-2) { + return lua_setfenv(L, idx); + } + inline 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)); @@ -480,8 +504,31 @@ namespace lua { } } + inline void store_in(lua::State* L, const std::string& tableName, const std::string& name) { + if (getglobal(L, tableName)) { + pushvalue(L, -2); + setfield(L, name); + pop(L, 2); + } else { + throw std::runtime_error("table "+tableName+" not found"); + } + } + + inline int get_from(lua::State* L, const std::string& tableName, const std::string& name, bool required=false) { + if (getglobal(L, tableName)) { + if (getfield(L, name)) { + return 1; + } else if (required) { + throw std::runtime_error("table "+tableName+" has no member "+name); + } + return 0; + } else { + throw std::runtime_error("table "+tableName+" not found"); + } + } + int call(lua::State*, int argc, int nresults=-1); - int call_nothrow(lua::State*, int argc); + int call_nothrow(lua::State*, int argc, int nresults=1); inline int eval(lua::State* L, int env, const std::string& src, const std::string& file="") { auto srcText = "return "+src; @@ -503,7 +550,7 @@ namespace lua { } return 0; } - int createEnvironment(lua::State*, int parent); + int create_environment(lua::State*, int parent); void removeEnvironment(lua::State*, int id); void dump_stack(lua::State*); diff --git a/src/logic/scripting/scripting.cpp b/src/logic/scripting/scripting.cpp index 126fdb5b..b33e1162 100644 --- a/src/logic/scripting/scripting.cpp +++ b/src/logic/scripting/scripting.cpp @@ -28,6 +28,8 @@ using namespace scripting; static debug::Logger logger("scripting"); +static inline const std::string STDCOMP = "stdcomp"; + Engine* scripting::engine = nullptr; Level* scripting::level = nullptr; const Content* scripting::content = nullptr; @@ -51,13 +53,15 @@ void scripting::initialize(Engine* engine) { load_script(fs::path("stdcmd.lua")); } +[[nodiscard]] scriptenv scripting::get_root_environment() { return std::make_shared(0); } +[[nodiscard]] scriptenv scripting::create_pack_environment(const ContentPack& pack) { auto L = lua::get_main_thread(); - int id = lua::createEnvironment(L, 0); + int id = lua::create_environment(L, 0); lua::pushenv(L, id); lua::pushvalue(L, -1); lua::setfield(L, "PACK_ENV"); @@ -70,9 +74,10 @@ scriptenv scripting::create_pack_environment(const ContentPack& pack) { }); } +[[nodiscard]] scriptenv scripting::create_doc_environment(const scriptenv& parent, const std::string& name) { auto L = lua::get_main_thread(); - int id = lua::createEnvironment(L, *parent); + int id = lua::create_environment(L, *parent); lua::pushenv(L, id); lua::pushvalue(L, -1); lua::setfield(L, "DOC_ENV"); @@ -95,6 +100,29 @@ scriptenv scripting::create_doc_environment(const scriptenv& parent, const std:: }); } +[[nodiscard]] +static scriptenv create_entity_environment(const scriptenv& parent, int entityIdx) { + auto L = lua::get_main_thread(); + int id = lua::create_environment(L, *parent); + + lua::pushvalue(L, entityIdx); + + lua::pushenv(L, id); + + lua::pushvalue(L, -1); + lua::setfield(L, "this"); + + lua::pushvalue(L, -2); + lua::setfield(L, "entity"); + + lua::pop(L, 2); + + return std::shared_ptr(new int(id), [=](int* id) { + lua::removeEnvironment(L, *id); + delete id; + }); +} + void scripting::process_post_runnables() { auto L = lua::get_main_thread(); if (lua::getglobal(L, "__process_post_runnables")) { @@ -227,25 +255,57 @@ bool scripting::on_item_break_block(Player* player, const ItemDef* item, int x, }); } -bool scripting::on_entity_spawn(const EntityDef& def, entityid_t eid) { - std::string name = def.name + ".spawn"; - return lua::emit_event(lua::get_main_thread(), name, [eid] (auto L) { - return lua::pushinteger(L, eid); - }); +scriptenv scripting::on_entity_spawn(const EntityDef& def, entityid_t eid, entity_funcs_set& funcsset) { + auto L = lua::get_main_thread(); + lua::requireglobal(L, STDCOMP); + if (lua::getfield(L, "new_Entity")) { + lua::pushinteger(L, eid); + lua::call(L, 1); + } + auto entityenv = create_entity_environment(get_root_environment(), -1); + lua::get_from(L, lua::CHUNKS_TABLE, def.scriptName, true); + lua::pushenv(L, *entityenv); + lua::setfenv(L); + lua::call_nothrow(L, 0, 0); + + lua::pushenv(L, *entityenv); + funcsset.on_grounded = lua::hasfield(L, "on_grounded"); + funcsset.on_despawn = lua::hasfield(L, "on_despawn"); + return entityenv; } -bool scripting::on_entity_despawn(const EntityDef& def, entityid_t eid) { - std::string name = def.name + ".despawn"; - return lua::emit_event(lua::get_main_thread(), name, [eid] (auto L) { - return lua::pushinteger(L, eid); - }); +static bool process_entity_callback( + const scriptenv& env, + const std::string& name, + std::function args +) { + auto L = lua::get_main_thread(); + lua::pushenv(L, *env); + if (lua::getfield(L, name)) { + if (args) { + lua::call_nothrow(L, args(L), 0); + } else { + lua::call_nothrow(L, 0, 0); + } + } + lua::pop(L); + return true; } -bool scripting::on_entity_grounded(const EntityDef& def, entityid_t eid) { - std::string name = def.name + ".grounded"; - return lua::emit_event(lua::get_main_thread(), name, [eid] (auto L) { - return lua::pushinteger(L, eid); - }); +bool scripting::on_entity_despawn(const EntityDef& def, const Entity& entity) { + const auto& script = entity.getScripting(); + if (script.funcsset.on_despawn) { + return process_entity_callback(script.env, "on_despawn", nullptr); + } + return true; +} + +bool scripting::on_entity_grounded(const EntityDef& def, const Entity& entity) { + const auto& script = entity.getScripting(); + if (script.funcsset.on_despawn) { + return process_entity_callback(script.env, "on_grounded", nullptr); + } + return true; } void scripting::on_ui_open( @@ -331,13 +391,12 @@ void scripting::load_item_script(const scriptenv& senv, const std::string& prefi funcsset.on_block_break_by = register_event(env, "on_block_break_by", prefix+".blockbreakby"); } -void scripting::load_entity_script(const scriptenv& senv, const std::string& prefix, const fs::path& file, entity_funcs_set& funcsset) { - int env = *senv; - load_script(env, "entity", file); - funcsset.init = register_event(env, "init", prefix+".init"); - funcsset.on_spawn = register_event(env, "on_spawn", prefix+".spawn"); - funcsset.on_despawn = register_event(env, "on_despawn", prefix+".despawn"); - funcsset.on_grounded = register_event(env, "on_grounded", prefix+".grounded"); +void scripting::load_entity_script(const scriptenv& penv, const EntityDef& def, const fs::path& file) { + auto L = lua::get_main_thread(); + std::string src = files::read_string(file); + logger.info() << "script (entity) " << file.u8string(); + lua::loadbuffer(L, 0, src, file); + lua::store_in(L, lua::CHUNKS_TABLE, def.scriptName); } void scripting::load_world_script(const scriptenv& senv, const std::string& prefix, const fs::path& file) { diff --git a/src/logic/scripting/scripting.hpp b/src/logic/scripting/scripting.hpp index 7bbd59d1..75a36f14 100644 --- a/src/logic/scripting/scripting.hpp +++ b/src/logic/scripting/scripting.hpp @@ -76,9 +76,9 @@ namespace scripting { /// @return true if prevents default action bool on_item_break_block(Player* player, const ItemDef* item, int x, int y, int z); - bool on_entity_spawn(const EntityDef& def, entityid_t eid); - bool on_entity_despawn(const EntityDef& def, entityid_t eid); - bool on_entity_grounded(const EntityDef& def, entityid_t eid); + scriptenv on_entity_spawn(const EntityDef& def, entityid_t eid, entity_funcs_set&); + bool on_entity_despawn(const EntityDef& def, const Entity& entity); + bool on_entity_grounded(const EntityDef& def, const Entity& entity); /// @brief Called on UI view show void on_ui_open( @@ -115,9 +115,8 @@ namespace scripting { void load_entity_script( const scriptenv& env, - const std::string& prefix, - const fs::path& file, - entity_funcs_set& funcsset); + const EntityDef& def, + const fs::path& file); /// @brief Load package-specific world script /// @param env environment diff --git a/src/objects/Entities.cpp b/src/objects/Entities.cpp index 1fed9c82..799d7f1a 100644 --- a/src/objects/Entities.cpp +++ b/src/objects/Entities.cpp @@ -36,16 +36,15 @@ entityid_t Entities::spawn(EntityDef& def, glm::vec3 pos) { registry.emplace(entity, static_cast(id), def); registry.emplace(entity, pos, size/4.0f, glm::mat3(1.0f)); registry.emplace(entity, true, Hitbox {pos, def.hitbox}); + auto& scripting = registry.emplace(entity, entity_funcs_set {}, nullptr); entities[id] = entity; - if (def.rt.funcsset.on_spawn) { - scripting::on_entity_spawn(def, id); - } + scripting.env = scripting::on_entity_spawn(def, id, scripting.funcsset); return id; } void Entities::despawn(entityid_t id) { if (auto entity = get(id)) { - scripting::on_entity_despawn(entity->getDef(), id); + scripting::on_entity_despawn(entity->getDef(), *entity); registry.destroy(get(id)->getHandler()); } } @@ -82,15 +81,16 @@ void Entities::updatePhysics(float delta){ transform.pos = hitbox.position; //transform.rot = glm::rotate(glm::mat4(transform.rot), delta, glm::vec3(0, 1, 0)); if (hitbox.grounded && !grounded) { - scripting::on_entity_grounded(eid.def, eid.uid); + scripting::on_entity_grounded(eid.def, *get(eid.uid)); } } } void Entities::renderDebug(LineBatch& batch) { batch.lineWidth(1.0f); - auto view = registry.view(); - for (auto [entity, transform, hitbox] : view.each()) { + auto view = registry.view(); + for (auto [entity, transform, rigidbody] : view.each()) { + const auto& hitbox = rigidbody.hitbox; batch.box(hitbox.position, hitbox.halfsize * 2.0f, glm::vec4(1.0f)); } } diff --git a/src/objects/Entities.hpp b/src/objects/Entities.hpp index 79df3e49..7cc49ab0 100644 --- a/src/objects/Entities.hpp +++ b/src/objects/Entities.hpp @@ -9,6 +9,12 @@ #include #include +struct entity_funcs_set { + bool init : 1; + bool on_despawn : 1; + bool on_grounded : 1; +}; + struct EntityDef; struct EntityId { @@ -30,6 +36,11 @@ struct Rigidbody { Hitbox hitbox; }; +struct Scripting { + entity_funcs_set funcsset; + scriptenv env; +}; + class Level; class Assets; class LineBatch; @@ -67,6 +78,10 @@ public: return registry.get(entity); } + Scripting& getScripting() const { + return registry.get(entity); + } + entityid_t getUID() const { return registry.get(entity).uid; } diff --git a/src/objects/EntityDef.hpp b/src/objects/EntityDef.hpp index c481e4ef..a409de09 100644 --- a/src/objects/EntityDef.hpp +++ b/src/objects/EntityDef.hpp @@ -6,13 +6,6 @@ #include "../typedefs.hpp" -struct entity_funcs_set { - bool init : 1; - bool on_spawn : 1; - bool on_despawn : 1; - bool on_grounded : 1; -}; - struct EntityDef { /// @brief Entity string id (with prefix included) std::string const name; @@ -22,8 +15,7 @@ struct EntityDef { struct { entityid_t id; - entity_funcs_set funcsset; - } rt; + } rt {}; EntityDef(const std::string& name) : name(name) {} EntityDef(const EntityDef&) = delete;