diff --git a/src/3rdparty/squirrel/include/squirrel.h b/src/3rdparty/squirrel/include/squirrel.h --- a/src/3rdparty/squirrel/include/squirrel.h +++ b/src/3rdparty/squirrel/include/squirrel.h @@ -361,6 +361,15 @@ void sq_setdebughook(HSQUIRRELVM v); #define sq_isweakref(o) ((o)._type==OT_WEAKREF) #define sq_type(o) ((o)._type) +/* Limit the total number of ops that can be consumed by an operation */ +struct SQOpsLimiter { + SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label); + ~SQOpsLimiter(); +private: + HSQUIRRELVM _v; + SQInteger _ops; +}; + /* deprecated */ #define sq_createslot(v,n) sq_newslot(v,n,SQFalse) diff --git a/src/3rdparty/squirrel/squirrel/sqapi.cpp b/src/3rdparty/squirrel/squirrel/sqapi.cpp --- a/src/3rdparty/squirrel/squirrel/sqapi.cpp +++ b/src/3rdparty/squirrel/squirrel/sqapi.cpp @@ -1323,3 +1323,16 @@ void sq_free(void *p,SQUnsignedInteger s SQ_FREE(p,size); } +SQOpsLimiter::SQOpsLimiter(HSQUIRRELVM v, SQInteger ops, const char *label) : _v(v) +{ + this->_ops = v->_ops_till_suspend_error_threshold; + if (this->_ops == INT64_MIN) { + v->_ops_till_suspend_error_threshold = v->_ops_till_suspend - ops; + v->_ops_till_suspend_error_label = label; + } +} + +SQOpsLimiter::~SQOpsLimiter() +{ + this->_v->_ops_till_suspend_error_threshold = this->_ops; +} diff --git a/src/script/api/ai_changelog.hpp b/src/script/api/ai_changelog.hpp --- a/src/script/api/ai_changelog.hpp +++ b/src/script/api/ai_changelog.hpp @@ -25,6 +25,11 @@ * \li AIError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * * Other changes: + * \li AIGroupList accepts an optional filter function + * \li AIIndustryList accepts an optional filter function + * \li AISignList accepts an optional filter function + * \li AISubsidyList accepts an optional filter function + * \li AITownList accepts an optional filter function * \li AIVehicleList accepts an optional filter function * * \b 13.0 diff --git a/src/script/api/game_changelog.hpp b/src/script/api/game_changelog.hpp --- a/src/script/api/game_changelog.hpp +++ b/src/script/api/game_changelog.hpp @@ -85,6 +85,11 @@ * \li GSError::ERR_PRECONDITION_TOO_MANY_PARAMETERS, that error is never returned anymore. * * Other changes: + * \li GSGroupList accepts an optional filter function + * \li GSIndustryList accepts an optional filter function + * \li GSSignList accepts an optional filter function + * \li GSSubsidyList accepts an optional filter function + * \li GSTownList accepts an optional filter function * \li GSVehicleList accepts an optional filter function * * \b 13.0 diff --git a/src/script/api/script_grouplist.cpp b/src/script/api/script_grouplist.cpp --- a/src/script/api/script_grouplist.cpp +++ b/src/script/api/script_grouplist.cpp @@ -14,11 +14,11 @@ #include "../../safeguards.h" -ScriptGroupList::ScriptGroupList() +ScriptGroupList::ScriptGroupList(HSQUIRRELVM vm) { EnforceCompanyModeValid_Void(); CompanyID owner = ScriptObject::GetCompany(); - for (const Group *g : Group::Iterate()) { - if (g->owner == owner) this->AddItem(g->index); - } + ScriptList::FillList(vm, this, + [owner](const Group *g) { return g->owner == owner; } + ); } diff --git a/src/script/api/script_grouplist.hpp b/src/script/api/script_grouplist.hpp --- a/src/script/api/script_grouplist.hpp +++ b/src/script/api/script_grouplist.hpp @@ -20,10 +20,35 @@ */ class ScriptGroupList : public ScriptList { public: +#ifdef DOXYGEN_API /** * @game @pre ScriptCompanyMode::IsValid(). */ ScriptGroupList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @game @pre ScriptCompanyMode::IsValid(). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * function IsType(group_id, type) + * { + * return ScriptGroup.GetVehicleType(group_id) == type; + * } + * ScriptGroupList(IsType, ScriptVehicle.VT_ROAD); + */ + ScriptGroupList(void *filter_function, int params, ...); +#else + /** + * The constructor wrapper from Squirrel. + */ + ScriptGroupList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; #endif /* SCRIPT_GROUPLIST_HPP */ diff --git a/src/script/api/script_industrylist.cpp b/src/script/api/script_industrylist.cpp --- a/src/script/api/script_industrylist.cpp +++ b/src/script/api/script_industrylist.cpp @@ -13,23 +13,21 @@ #include "../../safeguards.h" -ScriptIndustryList::ScriptIndustryList() +ScriptIndustryList::ScriptIndustryList(HSQUIRRELVM vm) { - for (const Industry *i : Industry::Iterate()) { - this->AddItem(i->index); - } + ScriptList::FillList(vm, this); } ScriptIndustryList_CargoAccepting::ScriptIndustryList_CargoAccepting(CargoID cargo_id) { - for (const Industry *i : Industry::Iterate()) { - if (i->IsCargoAccepted(cargo_id)) this->AddItem(i->index); - } + ScriptList::FillList(this, + [cargo_id](const Industry *i) { return i->IsCargoAccepted(cargo_id); } + ); } ScriptIndustryList_CargoProducing::ScriptIndustryList_CargoProducing(CargoID cargo_id) { - for (const Industry *i : Industry::Iterate()) { - if (i->IsCargoProduced(cargo_id)) this->AddItem(i->index); - } + ScriptList::FillList(this, + [cargo_id](const Industry *i) { return i->IsCargoProduced(cargo_id); } + ); } diff --git a/src/script/api/script_industrylist.hpp b/src/script/api/script_industrylist.hpp --- a/src/script/api/script_industrylist.hpp +++ b/src/script/api/script_industrylist.hpp @@ -19,7 +19,32 @@ */ class ScriptIndustryList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptIndustryList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * ScriptIndustryList(ScriptIndustry.HasDock); + * function IsType(industry_id, type) + * { + * return ScriptIndustry.GetIndustryType(industry_id) == type; + * } + * ScriptIndustryList(IsType, 0); + */ + ScriptIndustryList(void *filter_function, int params, ...); +#else + /** + * The constructor wrapper from Squirrel. + */ + ScriptIndustryList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; /** diff --git a/src/script/api/script_list.cpp b/src/script/api/script_list.cpp --- a/src/script/api/script_list.cpp +++ b/src/script/api/script_list.cpp @@ -11,9 +11,7 @@ #include "script_list.hpp" #include "script_controller.hpp" #include "../../debug.h" -#include "../../core/backup_type.hpp" #include "../../script/squirrel.hpp" -#include <../squirrel/sqvm.h> #include "../../safeguards.h" @@ -869,12 +867,7 @@ SQInteger ScriptList::Valuate(HSQUIRRELV ScriptObject::SetAllowDoCommand(false); /* Limit the total number of ops that can be consumed by a valuate operation */ - SQInteger new_ops_error_threshold = vm->_ops_till_suspend_error_threshold; - if (vm->_ops_till_suspend_error_threshold == INT64_MIN) { - new_ops_error_threshold = vm->_ops_till_suspend - MAX_VALUATE_OPS; - vm->_ops_till_suspend_error_label = "valuator function"; - } - AutoRestoreBackup ops_error_threshold_backup(vm->_ops_till_suspend_error_threshold, new_ops_error_threshold); + SQOpsLimiter limiter(vm, MAX_VALUATE_OPS, "valuator function"); /* Push the function to call */ sq_push(vm, 2); diff --git a/src/script/api/script_list.hpp b/src/script/api/script_list.hpp --- a/src/script/api/script_list.hpp +++ b/src/script/api/script_list.hpp @@ -42,6 +42,107 @@ private: bool initialized; ///< Whether an iteration has been started int modifications; ///< Number of modification that has been done. To prevent changing data while valuating. +protected: + template + static void FillList(ScriptList *list, ItemValid item_valid, ItemFilter item_filter) + { + for (const T *item : T::Iterate()) { + if (!item_valid(item)) continue; + if (!item_filter(item)) continue; + list->AddItem(item->index); + } + } + + template + static void FillList(ScriptList *list, ItemValid item_valid) + { + ScriptList::FillList(list, item_valid, [](const T *) { return true; }); + } + + template + static void FillList(ScriptList *list) + { + ScriptList::FillList(list, [](const T *) { return true; }); + } + + template + static void FillList(HSQUIRRELVM vm, ScriptList *list, ItemValid item_valid) + { + int nparam = sq_gettop(vm) - 1; + if (nparam >= 1) { + /* Make sure the filter function is really a function, and not any + * other type. It's parameter 2 for us, but for the user it's the + * first parameter they give. */ + SQObjectType valuator_type = sq_gettype(vm, 2); + if (valuator_type != OT_CLOSURE && valuator_type != OT_NATIVECLOSURE) { + throw sq_throwerror(vm, "parameter 1 has an invalid type (expected function)"); + } + + /* Push the function to call */ + sq_push(vm, 2); + } + + /* Don't allow docommand from a Valuator, as we can't resume in + * mid C++-code. */ + bool backup_allow = ScriptObject::GetAllowDoCommand(); + ScriptObject::SetAllowDoCommand(false); + + + if (nparam < 1) { + ScriptList::FillList(list, item_valid); + } else { + /* Limit the total number of ops that can be consumed by a filter operation, if a filter function is present */ + SQOpsLimiter limiter(vm, MAX_VALUATE_OPS, "list filter function"); + + ScriptList::FillList(list, item_valid, + [vm, nparam, backup_allow](const T *item) { + /* Push the root table as instance object, this is what squirrel does for meta-functions. */ + sq_pushroottable(vm); + /* Push all arguments for the valuator function. */ + sq_pushinteger(vm, item->index); + for (int i = 0; i < nparam - 1; i++) { + sq_push(vm, i + 3); + } + + /* Call the function. Squirrel pops all parameters and pushes the return value. */ + if (SQ_FAILED(sq_call(vm, nparam + 1, SQTrue, SQTrue))) { + ScriptObject::SetAllowDoCommand(backup_allow); + throw sq_throwerror(vm, "failed to run filter"); + } + + SQBool add = SQFalse; + + /* Retrieve the return value */ + switch (sq_gettype(vm, -1)) { + case OT_BOOL: + sq_getbool(vm, -1, &add); + break; + + default: + ScriptObject::SetAllowDoCommand(backup_allow); + throw sq_throwerror(vm, "return value of filter is not valid (not bool)"); + } + + /* Pop the return value. */ + sq_poptop(vm); + + return add; + } + ); + + /* Pop the filter function */ + sq_poptop(vm); + } + + ScriptObject::SetAllowDoCommand(backup_allow); + } + + template + static void FillList(HSQUIRRELVM vm, ScriptList *list) + { + ScriptList::FillList(vm, list, [](const T *) { return true; }); + } + public: typedef std::set ScriptItemList; ///< The list of items inside the bucket typedef std::map ScriptListBucket; ///< The bucket list per value diff --git a/src/script/api/script_signlist.cpp b/src/script/api/script_signlist.cpp --- a/src/script/api/script_signlist.cpp +++ b/src/script/api/script_signlist.cpp @@ -14,9 +14,9 @@ #include "../../safeguards.h" -ScriptSignList::ScriptSignList() +ScriptSignList::ScriptSignList(HSQUIRRELVM vm) { - for (const Sign *s : Sign::Iterate()) { - if (ScriptSign::IsValidSign(s->index)) this->AddItem(s->index); - } + ScriptList::FillList(vm, this, + [](const Sign *s) { return ScriptSign::IsValidSign(s->index); } + ); } diff --git a/src/script/api/script_signlist.hpp b/src/script/api/script_signlist.hpp --- a/src/script/api/script_signlist.hpp +++ b/src/script/api/script_signlist.hpp @@ -19,7 +19,29 @@ */ class ScriptSignList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptSignList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * function Contains(sign_id, str) + * { + * local name = ScriptSign.GetName(sign_id); + * return name != null && name.find(str) != null; + * } + * ScriptSignList(Contains, "something"); + */ + ScriptSignList(void *filter_function, int params, ...); +#else + ScriptSignList(HSQUIRRELVM); +#endif /* DOXYGEN_API */ }; #endif /* SCRIPT_SIGNLIST_HPP */ diff --git a/src/script/api/script_subsidylist.cpp b/src/script/api/script_subsidylist.cpp --- a/src/script/api/script_subsidylist.cpp +++ b/src/script/api/script_subsidylist.cpp @@ -13,9 +13,7 @@ #include "../../safeguards.h" -ScriptSubsidyList::ScriptSubsidyList() +ScriptSubsidyList::ScriptSubsidyList(HSQUIRRELVM vm) { - for (const Subsidy *s : Subsidy::Iterate()) { - this->AddItem(s->index); - } + ScriptList::FillList(vm, this); } diff --git a/src/script/api/script_subsidylist.hpp b/src/script/api/script_subsidylist.hpp --- a/src/script/api/script_subsidylist.hpp +++ b/src/script/api/script_subsidylist.hpp @@ -19,7 +19,28 @@ */ class ScriptSubsidyList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptSubsidyList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * function IsType(subsidy_id, type) + * { + * return ScriptSubsidy.GetSourceType(subsidy_id) == type; + * } + * ScriptSubsidyList(IsType, ScriptSubsidy.SPT_TOWN); + */ + ScriptSubsidyList(void *filter_function, int params, ...); +#else + ScriptSubsidyList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; #endif /* SCRIPT_SUBSIDYLIST_HPP */ diff --git a/src/script/api/script_townlist.cpp b/src/script/api/script_townlist.cpp --- a/src/script/api/script_townlist.cpp +++ b/src/script/api/script_townlist.cpp @@ -13,11 +13,9 @@ #include "../../safeguards.h" -ScriptTownList::ScriptTownList() +ScriptTownList::ScriptTownList(HSQUIRRELVM vm) { - for (const Town *t : Town::Iterate()) { - this->AddItem(t->index); - } + ScriptList::FillList(vm, this); } ScriptTownEffectList::ScriptTownEffectList() diff --git a/src/script/api/script_townlist.hpp b/src/script/api/script_townlist.hpp --- a/src/script/api/script_townlist.hpp +++ b/src/script/api/script_townlist.hpp @@ -19,7 +19,29 @@ */ class ScriptTownList : public ScriptList { public: +#ifdef DOXYGEN_API ScriptTownList(); + + /** + * Apply a filter when building the list. + * @param filter_function The function which will be doing the filtering. + * @param params The params to give to the filters (minus the first param, + * which is always the index-value). + * @note You can write your own filters and use them. Just remember that + * the first parameter should be the index-value, and it should return + * a bool. + * @note Example: + * ScriptTownList(ScriptTown.IsActionAvailable, ScriptTown.TOWN_ACTION_BRIBE); + * function MinPopulation(town_id, pop) + * { + * return ScriptTown.GetPopulation(town_id) >= pop; + * } + * ScriptTownList(MinPopulation, 1000); + */ + ScriptTownList(void *filter_function, int params, ...); +#else + ScriptTownList(HSQUIRRELVM vm); +#endif /* DOXYGEN_API */ }; /** diff --git a/src/script/api/script_vehiclelist.cpp b/src/script/api/script_vehiclelist.cpp --- a/src/script/api/script_vehiclelist.cpp +++ b/src/script/api/script_vehiclelist.cpp @@ -16,8 +16,6 @@ #include "../../vehicle_base.h" #include "../../vehiclelist_func.h" #include "../../train.h" -#include "../../core/backup_type.hpp" -#include <../squirrel/sqvm.h> #include "../../safeguards.h" @@ -25,84 +23,14 @@ ScriptVehicleList::ScriptVehicleList(HSQ { EnforceDeityOrCompanyModeValid_Void(); - int nparam = sq_gettop(vm) - 1; - if (nparam >= 1) { - /* Make sure the filter function is really a function, and not any - * other type. It's parameter 2 for us, but for the user it's the - * first parameter they give. */ - SQObjectType valuator_type = sq_gettype(vm, 2); - if (valuator_type != OT_CLOSURE && valuator_type != OT_NATIVECLOSURE) { - throw sq_throwerror(vm, "parameter 1 has an invalid type (expected function)"); - } - - /* Push the function to call */ - sq_push(vm, 2); - } - - /* Don't allow docommand from a Valuator, as we can't resume in - * mid C++-code. */ - bool backup_allow = ScriptObject::GetAllowDoCommand(); - ScriptObject::SetAllowDoCommand(false); - - /* Limit the total number of ops that can be consumed by a filter operation, if a filter function is present */ - SQInteger new_ops_error_threshold = vm->_ops_till_suspend_error_threshold; - if (nparam >= 1 && vm->_ops_till_suspend_error_threshold == INT64_MIN) { - new_ops_error_threshold = vm->_ops_till_suspend - MAX_VALUATE_OPS; - vm->_ops_till_suspend_error_label = "vehicle filter function"; - } - AutoRestoreBackup ops_error_threshold_backup(vm->_ops_till_suspend_error_threshold, new_ops_error_threshold); - bool is_deity = ScriptCompanyMode::IsDeity(); CompanyID owner = ScriptObject::GetCompany(); - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->owner != owner && !is_deity) continue; - if (!v->IsPrimaryVehicle() && !(v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())) continue; - if (nparam < 1) { - /* No filter, just add the item. */ - this->AddItem(v->index); - continue; - } - - /* Push the root table as instance object, this is what squirrel does for meta-functions. */ - sq_pushroottable(vm); - /* Push all arguments for the valuator function. */ - sq_pushinteger(vm, v->index); - for (int i = 0; i < nparam - 1; i++) { - sq_push(vm, i + 3); - } - - /* Call the function. Squirrel pops all parameters and pushes the return value. */ - if (SQ_FAILED(sq_call(vm, nparam + 1, SQTrue, SQTrue))) { - ScriptObject::SetAllowDoCommand(backup_allow); - throw sq_throwerror(vm, "failed to run filter"); + ScriptList::FillList(vm, this, + [is_deity, owner](const Vehicle *v) { + return (is_deity || v->owner == owner) && (v->IsPrimaryVehicle() || (v->type == VEH_TRAIN && ::Train::From(v)->IsFreeWagon())); } - - /* Retrieve the return value */ - switch (sq_gettype(vm, -1)) { - case OT_BOOL: { - SQBool add; - sq_getbool(vm, -1, &add); - if (add) this->AddItem(v->index); - break; - } - - default: { - ScriptObject::SetAllowDoCommand(backup_allow); - throw sq_throwerror(vm, "return value of filter is not valid (not bool)"); - } - } - - /* Pop the return value. */ - sq_poptop(vm); - } - - if (nparam >= 1) { - /* Pop the filter function */ - sq_poptop(vm); - } - - ScriptObject::SetAllowDoCommand(backup_allow); + ); } ScriptVehicleList_Station::ScriptVehicleList_Station(StationID station_id) @@ -182,11 +110,11 @@ ScriptVehicleList_Group::ScriptVehicleLi if (!ScriptGroup::IsValidGroup((ScriptGroup::GroupID)group_id)) return; CompanyID owner = ScriptObject::GetCompany(); - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->owner == owner && v->IsPrimaryVehicle()) { - if (v->group_id == group_id) this->AddItem(v->index); - } - } + + ScriptList::FillList(this, + [owner](const Vehicle *v) { return v->owner == owner && v->IsPrimaryVehicle(); }, + [group_id](const Vehicle *v) { return v->group_id == group_id; } + ); } ScriptVehicleList_DefaultGroup::ScriptVehicleList_DefaultGroup(ScriptVehicle::VehicleType vehicle_type) @@ -195,9 +123,9 @@ ScriptVehicleList_DefaultGroup::ScriptVe if (vehicle_type < ScriptVehicle::VT_RAIL || vehicle_type > ScriptVehicle::VT_AIR) return; CompanyID owner = ScriptObject::GetCompany(); - for (const Vehicle *v : Vehicle::Iterate()) { - if (v->owner == owner && v->IsPrimaryVehicle()) { - if (v->type == (::VehicleType)vehicle_type && v->group_id == ScriptGroup::GROUP_DEFAULT) this->AddItem(v->index); - } - } + + ScriptList::FillList(this, + [owner](const Vehicle *v) { return v->owner == owner && v->IsPrimaryVehicle(); }, + [vehicle_type](const Vehicle *v) { return v->type == (::VehicleType)vehicle_type && v->group_id == ScriptGroup::GROUP_DEFAULT; } + ); }