Real events:
Aren't validated
Speculated on being removed from being raised
Mod events:
Aren't validated
(not speculated to be removed?)
Both let the player pass in "garbage" (what is the garbage? Is it wrong information, or failing to assign information?)
Code: Select all
raise_event(event, table)
Raise an event.
Parameters
event :: uint: ID of the event to raise
table: Table with extra data. This table will be passed to the event handler.
I could use some further clarity on what is and isn't checked, if you would. This is a personal request, because I've ran into values being discarded, values causing errors, etc, so if there were any sense of structure with what's going on it'd make things easier. If some garbage can get through events but others can't, I'd unsurprisingly be confused.Rseding91 wrote: ↑Tue Nov 12, 2019 11:18 am I was thinking about what it would take to check everything passed through rase_event and it's just not a task that I think any sane human would want to sign up for. There are currently 144 events. The checks for all of them on the C++ side is around 3000 lines of code and that's not counting all of the functions they call themselves. The Lua version is typically 4 times larger due to all the mechanics of interacting with Lua data on the C++ side.
That's (at a guess) around 12'000 lines of code that someone would have to write all just to handle the rotten-eggs case of mods doing things wrong.
That doesn't make sense to me to do.
This kind of code is generable. Each type of value has a means of validating it (minus the exceptions when looking for a specific type of property, which makes it a different dictionary value anyway)Rseding91 wrote: ↑Tue Nov 26, 2019 1:37 pm People seem to be misunderstanding how the entire Lua API works... which Isn't too difficult to do since none of you can see it.
The game is C++. When a C++ "game event" happens the logic goes over the event listeners and lets them know it happened. That eventually makes its way into the "put the C++ data into the Lua state that's listening to this event and run the Lua event handler". For example - the entity-died event does that through this C++ function:
This function uses the C++ entity-died event data struct which looks like this:Code: Select all
//? Called when an entity dies. //? Can be filtered using $ref(LuaEntityDiedEventFilters) //? $param(entity, LuaEntity) //? $param(cause, LuaEntity, $optional) The entity that did the killing if available. //? $param(loot, LuaInventory) The loot generated by this entity if any. //? $param(force, LuaForce, $optional) The force that did the killing if any. bool LuaGameScript::pushOnEntityDied(const GameAction& event) { auto& data = event.getEntityDiedData(); if (data.entity->isToBeDeleted()) return false; // entity new LuaEntity(data.entity, this->luaState); lua_setfield(this->luaState, -2, "entity"); // cause if (data.cause && !data.cause->isToBeDeleted()) { new LuaEntity(data.cause, this->luaState); lua_setfield(this->luaState, -2, "cause"); } // loot new LuaInventory(data.inventory, this->getMap(), this->luaState); lua_setfield(this->luaState, -2, "loot"); // force if (!data.forceID.isZero() && this->getMap().forceManager.getForces().has(data.forceID)) { new LuaForce(this->getMap(), data.forceID, this->luaState); lua_setfield(this->luaState, -2, "force"); } return true; }
On the Lua side it gets:Code: Select all
struct EntityDiedData { EntityWithHealth* entity = nullptr; Entity* cause = nullptr; TargetableInventory* inventory = nullptr; ForceID forceID; };
entity = LuaEntity (the entity that died)
cause = LuaEntity (the entity that killed the entity - optional)
loot = LuaInvnetory (the loot generated by this entity dying)
force = LuaForce (the force that killed this entity - optional)
Now, that function takes a C++ struct and creates the Lua wrappers that get passed to the Lua event handler. That is a 1-way trip. You can't just use that same function to "validate the data gotten from lua is correct for this event".
I've written the function that would have to be used to do that validation just for this example. Note the function isn't actually complete because there are parts that simply don't work. Also note it took me 20 minutes to write this and I already knew literally every line I would have to write and where every function was that I would need to use.
Code: Select all
int32_t LuaBootstrap::luaRaiseEntityDied(lua_State* L) { LuaHelper::argCheck(L, 1); luaL_checktype(L, 1, LUA_TTABLE); GameActionData::EntityDiedData eventData; lua_getfield(L, 1, "entity"); Entity* entity = LuaHelper::parseEntity(L, -1, nullptr); lua_pop(L, 1); if (!entity->isEntityWithHealth()) throw ScriptException("Given entity is not an entity with health."); eventData.entity = static_cast<EntityWithHealth*>(entity); lua_getfield(L, 1, "cause"); if (!lua_isnil(L, -1)) eventData.cause = LuaHelper::parseOptionalEntity(L, -1, nullptr); lua_pop(L, 1); lua_getfield(L, 1, "loot"); luaL_checktype(L, -1, LUA_TTABLE); LuaInventory* luaInventory = dynamic_cast<LuaInventory*>(LuaHelper::parseLuaBindableObject(L, -1)); lua_pop(L, 1); if (!luaInventory) throw ScriptException("Given object is not a LuaInventory."); if (!luaInventory->valid()) throw ScriptException("Given inventory doesn't exist anymore."); // This doesn't work because the event is setup to store TargetableInventory* and there's currently no way to get that back from LuaInventory // I would have to go add a way to get TargetableInventory* from LuaInventory (can be done) // eventData.inventory = luaInventory->getTargetableInventory(); lua_getfield(L, 1, "force"); if (!lua_isnil(L, -1)) eventData.forceID = LuaHelper::parseForce(L, this->script->getMap(), -1); lua_pop(L, 1); this->script->eventDispatcher->run(LuaEventType::ON_ENTITY_DIED, this->script->getMap().entityTick, &LuaGameScript::pushOnEntityDied, GameAction(GameAction::EntityDied, eventData)); return 0; }
Thank you for sharing it actually, it's coded "regularly" but it does give some insights.
I have already written professional code which took a data file, processed it in Perl, and barfed it out in C to get around no dynamic allocation in our context. Fun stuff.
Edits. Of course.