Lua API: Add __dir metafunction to schedule objects

This covers both wesnoth.current.schedule and the objects returned by wesnoth.map.get_area()

Also, the wesnoth.schedule module is no longer treated like the schedule metatable, since none of the functions in the module take a schedule as the first argument. This may be reverted in the future.

The attribute registration system has also been extended to permit registry tables to conditionally add certain keys.
This commit is contained in:
Celtic Minstrel 2024-09-11 23:58:16 -04:00 committed by Celtic Minstrel
parent facf4eefda
commit d7eba7b950
5 changed files with 173 additions and 78 deletions

View File

@ -906,6 +906,7 @@ void game_lua_kernel::luaW_push_schedule(lua_State* L, int area_index)
static luaL_Reg const schedule_meta[] {
{"__index", &dispatch<&game_lua_kernel::impl_schedule_get>},
{"__newindex", &dispatch<&game_lua_kernel::impl_schedule_set>},
{"__dir", &dispatch<&game_lua_kernel::impl_schedule_dir>},
{"__len", &dispatch<&game_lua_kernel::impl_schedule_len>},
{ nullptr, nullptr }
};
@ -924,6 +925,26 @@ static int luaW_check_schedule(lua_State* L, int idx)
return i;
}
struct schedule_tag {
game_lua_kernel& ref;
int area_index;
schedule_tag(game_lua_kernel& k) : ref(k) {}
auto& tod_man() const { return ref.tod_man(); }
};
#define SCHEDULE_GETTER(name, type) LATTR_GETTER(name, type, schedule_tag, sched)
#define SCHEDULE_SETTER(name, type) LATTR_SETTER(name, type, schedule_tag, sched)
#define SCHEDULE_VALID(name) LATTR_VALID(name, schedule_tag, sched)
luaW_Registry scheduleReg{"schedule"};
template<> struct lua_object_traits<schedule_tag> {
inline static auto metatable = "schedule";
inline static schedule_tag get(lua_State* L, int n) {
schedule_tag sched{lua_kernel_base::get_lua_kernel<game_lua_kernel>(L)};
sched.area_index = luaW_check_schedule(L, n);
return sched;
}
};
int game_lua_kernel::impl_schedule_get(lua_State *L)
{
int area_index = luaW_check_schedule(L, 1);
@ -935,26 +956,8 @@ int game_lua_kernel::impl_schedule_get(lua_State *L)
}
luaW_push_tod(L, times[i]);
return 1;
} else {
const char* m = luaL_checkstring(L, 2);
if(area_index >= 0) {
return_string_attrib("time_of_day", tod_man().get_area_time_of_day(area_index).id);
return_string_attrib("id", tod_man().get_area_id(area_index));
if(strcmp(m, "hexes") == 0) {
const auto& hexes = tod_man().get_area_by_index(area_index);
luaW_push_locationset(L, hexes);
return 1;
}
} else {
return_string_attrib("time_of_day", tod_man().get_time_of_day().id);
return_int_attrib("liminal_bonus", tod_man().get_max_liminal_bonus());
}
if(luaW_getglobal(L, "wesnoth", "schedule", m)) {
return 1;
}
}
return 0;
return scheduleReg.get(L);
}
int game_lua_kernel::impl_schedule_len(lua_State *L)
@ -981,59 +984,105 @@ int game_lua_kernel::impl_schedule_set(lua_State *L)
} else {
tod_man().replace_local_schedule(times, area_index);
}
} else {
const char* m = luaL_checkstring(L, 2);
if(strcmp(m, "time_of_day") == 0) {
std::string value = luaL_checkstring(L, 3);
const auto& times = area_index < 0 ? tod_man().times() : tod_man().times(area_index);
auto iter = std::find_if(times.begin(), times.end(), [&value](const time_of_day& tod) {
return tod.id == value;
});
if(iter == times.end()) {
std::ostringstream err;
err << "invalid time of day ID for ";
if(area_index < 0) {
err << "global schedule";
} else {
const std::string& id = tod_man().get_area_id(area_index);
if(id.empty()) {
const auto& hexes = tod_man().get_area_by_index(area_index);
if(hexes.empty()) {
err << "anonymous empty time area";
} else {
err << "anonymous time area at (" << hexes.begin()->wml_x() << ',' << hexes.begin()->wml_y() << ")";
}
} else {
err << "time area with id=" << id;
}
}
lua_push(L, err.str());
return lua_error(L);
}
int n = std::distance(times.begin(), iter);
if(area_index < 0) {
tod_man().set_current_time(n);
} else {
tod_man().set_current_time(n, area_index);
}
}
if(area_index >= 0) {
modify_string_attrib("id", tod_man().set_area_id(area_index, value));
if(strcmp(m, "hexes") == 0) {
auto hexes = luaW_check_locationset(L, 3);
tod_man().replace_area_locations(area_index, hexes);
return 0;
}
} else {
// Assign nil to reset the bonus to the default (best) value
if(lua_isnil(L, 3) && strcmp(m, "liminal_bonus") == 0) {
tod_man().reset_max_liminal_bonus();
return 0;
}
modify_int_attrib("liminal_bonus", tod_man().set_max_liminal_bonus(value));
}
}
return 0;
return scheduleReg.set(L);
}
int game_lua_kernel::impl_schedule_dir(lua_State *L) {
return scheduleReg.dir(L);
}
SCHEDULE_GETTER("time_of_day", std::string) {
if(sched.area_index >= 0) {
return sched.tod_man().get_area_time_of_day(sched.area_index).id;
}
return sched.tod_man().get_time_of_day().id;
}
SCHEDULE_SETTER("time_of_day", std::string) {
const auto& times = sched.area_index < 0 ? sched.tod_man().times() : sched.tod_man().times(sched.area_index);
auto iter = std::find_if(times.begin(), times.end(), [&value](const time_of_day& tod) {
return tod.id == value;
});
if(iter == times.end()) {
std::ostringstream err;
err << "invalid time of day ID for ";
if(sched.area_index < 0) {
err << "global schedule";
} else {
const std::string& id = sched.tod_man().get_area_id(sched.area_index);
if(id.empty()) {
const auto& hexes = sched.tod_man().get_area_by_index(sched.area_index);
if(hexes.empty()) {
err << "anonymous empty time area";
} else {
err << "anonymous time area at (" << hexes.begin()->wml_x() << ',' << hexes.begin()->wml_y() << ")";
}
} else {
err << "time area with id=" << id;
}
}
lua_push(L, err.str());
throw lua_error(L);
}
int n = std::distance(times.begin(), iter);
if(sched.area_index < 0) {
sched.tod_man().set_current_time(n);
} else {
sched.tod_man().set_current_time(n, sched.area_index);
}
}
SCHEDULE_VALID("liminal_bonus") {
return sched.area_index < 0;
}
SCHEDULE_GETTER("liminal_bonus", utils::optional<int>) {
if(sched.area_index >= 0) return utils::nullopt;
return sched.tod_man().get_max_liminal_bonus();
}
SCHEDULE_SETTER("liminal_bonus", utils::optional<int>) {
if(sched.area_index >= 0) {
throw luaL_error(L, "liminal_bonus can only be set on the global schedule");
}
if(value) {
sched.tod_man().set_max_liminal_bonus(*value);
} else {
sched.tod_man().reset_max_liminal_bonus();
}
}
SCHEDULE_VALID("id") {
return sched.area_index >= 0;
}
SCHEDULE_GETTER("id", utils::optional<std::string>) {
if(sched.area_index < 0) return utils::nullopt;
return sched.tod_man().get_area_id(sched.area_index);
}
SCHEDULE_SETTER("id", std::string) {
if(sched.area_index < 0) {
throw luaL_error(L, "can't set id of global schedule");
}
sched.tod_man().set_area_id(sched.area_index, value);
}
SCHEDULE_VALID("hexes") {
return sched.area_index >= 0;
}
SCHEDULE_GETTER("hexes", utils::optional<std::set<map_location>>) {
if(sched.area_index < 0) return utils::nullopt;
return sched.tod_man().get_area_by_index(sched.area_index);
}
SCHEDULE_SETTER("hexes", std::set<map_location>) {
if(sched.area_index < 0) {
throw luaL_error(L, "can't set hexes of global schedule");
}
sched.tod_man().replace_area_locations(sched.area_index, value);
}
/**

View File

@ -70,6 +70,7 @@ class game_lua_kernel : public lua_kernel_base
friend class game_config_manager; // to allow it to call extract_preload_scripts
friend struct current_tag;
friend struct scenario_tag;
friend struct schedule_tag;
// Private lua callbacks
int intf_allow_end_turn(lua_State *);
@ -174,6 +175,7 @@ class game_lua_kernel : public lua_kernel_base
int intf_redraw(lua_State *L);
int intf_replace_schedule(lua_State *l);
int impl_schedule_set(lua_State *L);
int impl_schedule_dir(lua_State *L);
int intf_scroll(lua_State *L);
int intf_get_all_vars(lua_State *L);
int impl_theme_item(lua_State *L, std::string name);

View File

@ -67,14 +67,23 @@ int luaW_Registry::set(lua_State* L) {
int luaW_Registry::dir(lua_State *L) {
std::vector<std::string> keys;
// Check for inactive keys
std::set<std::string> inactive;
for(const auto& [key, func] : validators) {
if(!func(L)) {
inactive.insert(key);
}
}
// Add any readable keys
for(const auto& [key, func] : getters) {
if(inactive.count(key) > 0) continue;
if(func(L, true)){
keys.push_back(key);
}
}
// Add any writable keys
for(const auto& [key, func] : setters) {
if(inactive.count(key) > 0) continue;
if(func(L, 0, true)){
keys.push_back(key);
}

View File

@ -41,6 +41,9 @@ struct luaW_Registry {
using setters_list = std::map<std::string, std::function<bool(lua_State* L,int idx,bool nop)>>;
/// A map of callbacks that write data to the object.
setters_list setters;
using validators_list = std::map<std::string, std::function<bool(lua_State* L)>>;
/// A map of callbacks that check if a member is available.
validators_list validators;
/// The internal metatable string for the object (from __metatable)
std::string private_metatable;
/// Optional external metatable for the object (eg "wesnoth", "units")
@ -57,6 +60,8 @@ struct luaW_Registry {
int dir(lua_State* L);
};
enum class lua_attrfunc_type { getter, setter, validator };
template<typename object_type, typename value_type>
struct lua_getter
{
@ -71,17 +76,24 @@ struct lua_setter
virtual ~lua_setter() = default;
};
template<typename object_type>
struct lua_validator
{
virtual bool is_active(lua_State* L, const object_type& obj) const = 0;
virtual ~lua_validator() = default;
};
template<typename T> struct lua_object_traits;
template<typename object_type, typename value_type, typename action_type, bool setter>
template<typename object_type, typename value_type, typename action_type, lua_attrfunc_type type>
void register_lua_attribute(const char* name)
{
using obj_traits = lua_object_traits<object_type>;
using map_type = std::conditional_t<setter, luaW_Registry::setters_list, luaW_Registry::getters_list>;
using map_type = std::conditional_t<type == lua_attrfunc_type::validator, luaW_Registry::validators_list, std::conditional_t<type == lua_attrfunc_type::setter, luaW_Registry::setters_list, luaW_Registry::getters_list>>;
using callback_type = typename map_type::mapped_type;
map_type* map;
callback_type fcn;
if constexpr(setter) {
if constexpr(type == lua_attrfunc_type::setter) {
map = &luaW_Registry::lookup.at(obj_traits::metatable).get().setters;
fcn = [action = action_type()](lua_State* L, int idx, bool nop) {
if(nop) return true;
@ -89,13 +101,18 @@ void register_lua_attribute(const char* name)
action.set(L, obj, lua_check<value_type>(L, idx));
return true;
};
} else {
} else if constexpr(type == lua_attrfunc_type::getter) {
map = &luaW_Registry::lookup.at(obj_traits::metatable).get().getters;
fcn = [action = action_type()](lua_State* L, bool nop) {
if(nop) return true;
lua_push(L, action.get(L, obj_traits::get(L, 1)));
return true;
};
} else if constexpr(type == lua_attrfunc_type::validator) {
map = &luaW_Registry::lookup.at(obj_traits::metatable).get().validators;
fcn = [action = action_type()](lua_State* L) {
return action.is_active(L, obj_traits::get(L, 1));
};
}
(*map)[std::string(name)] = fcn;
}
@ -110,7 +127,7 @@ struct LATTR_MAKE_UNIQUE_ID(getter_, id, obj_name) : public lua_getter<obj_type,
struct LATTR_MAKE_UNIQUE_ID(getter_adder_, id, obj_name) { \
LATTR_MAKE_UNIQUE_ID(getter_adder_, id, obj_name) () \
{ \
register_lua_attribute<obj_type, value_type, LATTR_MAKE_UNIQUE_ID(getter_, id, obj_name), false>(name); \
register_lua_attribute<obj_type, value_type, LATTR_MAKE_UNIQUE_ID(getter_, id, obj_name), lua_attrfunc_type::getter>(name); \
} \
}; \
static LATTR_MAKE_UNIQUE_ID(getter_adder_, id, obj_name) LATTR_MAKE_UNIQUE_ID(getter_adder_instance_, id, obj_name) ; \
@ -125,13 +142,28 @@ struct LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name) : public lua_setter<obj_type,
struct LATTR_MAKE_UNIQUE_ID(setter_adder_, id, obj_name) { \
LATTR_MAKE_UNIQUE_ID(setter_adder_, id, obj_name) ()\
{ \
register_lua_attribute<obj_type, value_type, LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name), true>(name); \
register_lua_attribute<obj_type, value_type, LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name), lua_attrfunc_type::setter>(name); \
} \
}; \
static LATTR_MAKE_UNIQUE_ID(setter_adder_, id, obj_name) LATTR_MAKE_UNIQUE_ID(setter_adder_instance_, id, obj_name); \
void LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name)::set([[maybe_unused]] lua_State* L, LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name)::object_type& obj_name, const value_type& value) const
#define LATTR_VALID5(name, obj_type, obj_name, id) \
struct LATTR_MAKE_UNIQUE_ID(check_, id, obj_name) : public lua_validator<obj_type> { \
using object_type = obj_type; \
bool is_active(lua_State* L, const object_type& obj_name) const override; \
}; \
struct LATTR_MAKE_UNIQUE_ID(check_adder_, id, obj_name) { \
LATTR_MAKE_UNIQUE_ID(check_adder_, id, obj_name) ()\
{ \
register_lua_attribute<obj_type, void, LATTR_MAKE_UNIQUE_ID(check_, id, obj_name), lua_attrfunc_type::validator>(name); \
} \
}; \
static LATTR_MAKE_UNIQUE_ID(check_adder_, id, obj_name) LATTR_MAKE_UNIQUE_ID(check_adder_instance_, id, obj_name); \
bool LATTR_MAKE_UNIQUE_ID(check_, id, obj_name)::is_active([[maybe_unused]] lua_State* L, const LATTR_MAKE_UNIQUE_ID(check_, id, obj_name)::object_type& obj_name) const
/**
* @param name: string attribute name
* @param value_type: the type of the attribute, for example int or std::string
@ -141,3 +173,5 @@ void LATTR_MAKE_UNIQUE_ID(setter_, id, obj_name)::set([[maybe_unused]] lua_State
#define LATTR_GETTER(name, value_type, obj_type, obj_name) LATTR_GETTER5(name, value_type, obj_type, obj_name, __LINE__)
#define LATTR_SETTER(name, value_type, obj_type, obj_name) LATTR_SETTER5(name, value_type, obj_type, obj_name, __LINE__)
#define LATTR_VALID(name, obj_type, obj_name) LATTR_VALID5(name, obj_type, obj_name, __LINE__)

View File

@ -326,7 +326,8 @@ namespace lua_check_impl
for (int i = 1, i_end = lua_rawlen(L, n); i <= i_end; ++i)
{
lua_rawgeti(L, n, i);
res.push_back(lua_check_impl::lua_check<std::decay_t<typename T::reference>>(L, -1));
// By using insert instead of push_back, it magically "just works" for sets too.
res.insert(res.end(), lua_check_impl::lua_check<std::decay_t<typename T::reference>>(L, -1));
lua_pop(L, 1);
}
return res;