Page 1 of 1

Event request: on_recipe_changed

Posted: Tue Jul 03, 2018 10:48 am
by lovely_santa
Hi RSeding,

I have to keep track of all my buildings that have a set recipe.
To keep track of the active recipes there are 3 options:
- When a player/bot places a building over a ghost that has a recipe set (easy)
- When a player uses the copy/paste setting (easy)
- When a player changes the recipe manualy (though).

On that last one, I can't look on_gui_closed becose the gui doesn't have to be explicitly closed by the player, when keeping track of the open gui of the player is very inefficient.

I think this is reason enough for this event?
Becose we might be able to check it when the gui is closed, but thats not the same time as when the player changes the recipe, for example, when the player resets the recipe, the player gains items in the input/output buffer of the machine, so we can't take action when that recipe changes (there is no on_inventory_changed event either that would give a perfect solution, but even then recipes could be changed at times that the player wouldn't gain items), so i realy think this is lacking some event handling...

Kind regards,
lovely_santa

Re: Event request: on_recipe_changed

Posted: Tue Jul 03, 2018 10:51 am
by Godmave
Or enabling the close event for any closing, not just explicit, would help me (use_colors change for lamps) and probably others too.

Re: Event request: on_recipe_changed

Posted: Sun Jul 08, 2018 12:12 am
by Rseding91
Why exactly do you want to track what machines have what recipe set?

Re: Event request: on_recipe_changed

Posted: Mon Jul 09, 2018 7:22 pm
by lovely_santa
Yes, I want to track what an assembling machine type of entity has set as recipe. Depending on the recipe I need to take actions.
Once the machine is backed up, I can put the machine in a sleep-like mode, more like a code optimalisation. But when the recipe changes I have to take actions again. But there is no event (or series of events) that can give me real time information about the recipe beiing set/unset (mostly the set is an issue).

Thats why i want to have an event on_recipe_changed that holds the entity and player who changed the recipe. An optional parameter would be the recipe that has been set (or nil when it was removed), but that i can check on the entity itself, so that would be obsolete.

Kind regards,
lovely_santa

Re: Event request: on_recipe_changed

Posted: Mon Jul 09, 2018 8:58 pm
by Rseding91
But *why* do you want to do that?

Re: Event request: on_recipe_changed

Posted: Tue Jul 10, 2018 7:05 pm
by lovely_santa
Becose when the assembler is done crafting something, i have to take actions (manualy removing output, placing stuff on the surface as result). When the recipe gets changed, i also have to remove placed down stuff.. Its for in a vehicle mod and also some robot mod

EDIT: afther discord conversation:
Why: I want to extend the output buffer behaviour of a machine, so it crafts some items up till the output buffer has some recipes build. At the end of that, i take some out and place an entity inside the world, that is part of the output buffer.

There are alredy events in place for copy/paste, placing/destroying entities, ghosts, but there are no events when the player changes the recipe manualy.
Klonan said that this was reasonable, but that I could set a fixed recipe.
I answered if there are some mods involved i would have to make a building for each available recipe, so that wouldn't be a solution.

Klonan found it a reasonable request, that could open up some possibilities, mainly of not hassling the game on_tick to see if te recipes are the same
  • Tracking recipes
  • When player sets special recipe, insert ingredients so its crafting automaticly
  • autofill when you change recipes (like the mod doiing it for turrets)
  • ...
I hope this answers the *why* question beter?

Re: Event request: on_recipe_changed

Posted: Tue Oct 02, 2018 10:42 pm
by Rseding91
Those reasons don't justify the performance overhead and maintenance to implement that event.

Re: Event request: on_recipe_changed

Posted: Fri Oct 05, 2018 8:17 pm
by Mylon
How often does a recipe change? The number of times such an event fires ought to be tiny. Maybe 10,000 calls throughout an entire game?

My particular usecase:

I made a scenario called Regional Production. Goods can only be produced near a matching marker. I want a on_recipe_changed event so I can say "Not allowed" and set the recipe to nil. As opposed to currently where I use a worker to iterate over all assemblers and if one has the wrong recipe I correct it them.

Re: Event request: on_recipe_changed

Posted: Fri Oct 05, 2018 9:38 pm
by Rseding91
The main issue is the added complexity to protect against mods doing 'things' during the event that can invalidate parts of the game.

Right now we don't have any protection in place around setting recipes in assembling machines because it's not possible for a mod to break anything. If I add an event then I need to go over every possible place in code where an assembling machine recipe can be set and make sure it properly handles if *anything* is invalidated/changed.

Re: Event request: on_recipe_changed

Posted: Sat Oct 06, 2018 3:45 am
by Mylon
I'm confused. Aren't these protections already in place around LuaEntity.set_recipe()? I already can mess with the bits I need and this function seems to perform fine. I'm not sure how on_recipe_changed event would be any different than a very aggressive on_tick check_all_assemblers() function.

Re: Event request: on_recipe_changed

Posted: Sat Oct 06, 2018 6:06 am
by Rseding91
This is (one of) the call stacks for how an assembling machine can have its recipe set:
> factorio-run.exe!CraftingMachine::setRecipeID(ID<RecipePrototype,unsigned short> recipeID, CraftingMachine::FromScript fromScript, CraftingMachine::RefreshEffect refreshEffect) Line 639 C++
factorio-run.exe!AssemblingMachine::setRecipeID(ID<RecipePrototype,unsigned short> recipeID, CraftingMachine::FromScript fromScript, CraftingMachine::RefreshEffect refreshEffect) Line 554 C++
factorio-run.exe!AssemblingMachine::setupForCrafting(ID<RecipePrototype,unsigned short> recipeID, InventoryBuffer & buffer, CraftingMachine::FromScript fromScript, AssemblingMachine::Force force) Line 403 C++
factorio-run.exe!AssemblingMachine::copyEntitySettings(const CopyEntitySettingsData & data) Line 468 C++
factorio-run.exe!BlueprintDataHandler::copyEntitySettings(Entity * equalEntity, const Entity * entity, const Direction direction) Line 185 C++
factorio-run.exe!BlueprintEntities::build::__l2::<lambda>(Entity * source, BuildCheckResult & buildableTest, Entity * entity) Line 464 C++
factorio-run.exe!BlueprintEntities::build(BlueprintDataHandler & dataHandler, const Vector & shift, Direction direction, bool altBuild, bool skipFogOfWar) Line 518 C++
factorio-run.exe!Blueprint::tryToBuild(BlueprintDataHandler & dataHandler, const MapPosition & position, Direction direction, bool altBuild, bool skipFogOfWar) Line 394 C++
factorio-run.exe!ManualBuilder::buildBlueprint(const MapPosition & placeToPut, Direction direction, BlueprintItem & blueprintItem, CreatedByMoving __formal, bool altBuild, bool skipFogOfWar) Line 1374 C++
factorio-run.exe!ManualBuilder::buildItem(const ActionData::BuildItemParameters & buildInfo, ForceID forceID, ID<EntityPrototype,unsigned short> rawEntityID, BuildItemSettings settings, UndoItem * undoItem) Line 575 C++
factorio-run.exe!CharacterController::buildItem(const ActionData::BuildItemParameters & buildInfo) Line 574 C++
factorio-run.exe!GameActionHandler::buildItem(const InputAction & event, Controller * controller) Line 634 C++
factorio-run.exe!GameActionHandler::actionPerformed(const InputAction & event) Line 305 C++
factorio-run.exe!InputHandler::flushToListeners(const InputAction & action, bool isStopped) Line 73 C++
factorio-run.exe!InputHandler::flushActions(bool isStopped, unsigned int tick) Line 65 C++
factorio-run.exe!InputHandler::nextTick(unsigned int tick) Line 53 C++
factorio-run.exe!GameActionHandler::update() Line 346 C++
factorio-run.exe!MainLoop::gameUpdateStep(MultiplayerManagerBase * multiplayerManagerBase, Scenario * scenario, AppManager * appManager, MainLoop::HeavyMode heavyMode) Line 1009 C++
factorio-run.exe!MainLoop::gameUpdateLoop(MainLoop::HeavyMode heavyMode) Line 881 C++
factorio-run.exe!MainLoop::mainLoopStep::__l2::<lambda>() Line 555 C++
[External Code]
factorio-run.exe!WorkerThread::loop() Line 42 C++
[External Code]
factorio-run.exe!invoke_thread_procedure(unsigned int(*)(void *) procedure, void * const context) Line 92 C++
factorio-run.exe!thread_start<unsigned int (__cdecl*)(void * __ptr64)>(void * const parameter) Line 115 C++
That's deep inside the build logic for building a blueprint. And that deep down it would then fire a Lua event which can invalidate any entity, any item, add or remove forces, and so on.

Every single site that calls into another function deep would need its own protection to check if everything is still valid after the function finished if that event existed.

Now compare that to the on_tick event:
> factorio-run.exe!LuaEventDispatcher::dispatch(unsigned int tick) Line 93 C++
factorio-run.exe!LuaContext::update() Line 130 C++
factorio-run.exe!Scenario::update() Line 1070 C++
factorio-run.exe!Scenario::updateStep() Line 970 C++
factorio-run.exe!MainLoop::gameUpdateStep(MultiplayerManagerBase * multiplayerManagerBase, Scenario * scenario, AppManager * appManager, MainLoop::HeavyMode heavyMode) Line 1016 C++
factorio-run.exe!MainLoop::gameUpdateLoop(MainLoop::HeavyMode heavyMode) Line 881 C++
factorio-run.exe!MainLoop::mainLoopStep::__l2::<lambda>() Line 555 C++
[External Code]
factorio-run.exe!WorkerThread::loop() Line 42 C++
[External Code]
factorio-run.exe!invoke_thread_procedure(unsigned int(*)(void *) procedure, void * const context) Line 92 C++
factorio-run.exe!thread_start<unsigned int (__cdecl*)(void * __ptr64)>(void * const parameter) Line 115 C++
[External Code]
It has nothing it needs to check. Regardless of what the on_tick handler does the game is left in a valid state and the code just returns having finished the event.

Re: Event request: on_recipe_changed

Posted: Sat Oct 06, 2018 3:01 pm
by Mylon
How about a different approach? What if recipe_changed toggled a flag on crafting machines and the event is raised in the next crafting_machine::update?

Also, following other events, LuaCraftingMachine.setRecipe() wouldn't raise the event much like how other lua calls don't raise events.

Re: Event request: on_recipe_changed

Posted: Sat Oct 06, 2018 5:37 pm
by Rseding91
Mylon wrote:
Sat Oct 06, 2018 3:01 pm
How about a different approach? What if recipe_changed toggled a flag on crafting machines and the event is raised in the next crafting_machine::update?

Also, following other events, LuaCraftingMachine.setRecipe() wouldn't raise the event much like how other lua calls don't raise events.
The machine would then need to store the fact the recipe changed + what the old recipe was in memory and in the save file. Neither of which are acceptable.

Re: Event request: on_recipe_changed

Posted: Mon Oct 08, 2018 3:46 pm
by Mylon
I wouldn't imagine an extra bool per crafting machine would be a big deal for the save. As for the previous recipe... I'm not sure that's necessary either. At least for my use case, all I need to know is if the current recipe follows a rule. Testing the machine periodically just in case the recipe changed makes the rule checking unresponsive.

Re: Event request: on_recipe_changed

Posted: Fri Oct 19, 2018 2:36 am
by Avacado
To keep track of the active recipes there are 3 options:
- When a player/bot places a building over a ghost that has a recipe set (easy)
- When a player uses the copy/paste setting (easy)
- When a player changes the recipe manualy (though).

On that last one, I can't look on_gui_closed becose the gui doesn't have to be explicitly closed by the player, when keeping track of the open gui of the player is very inefficient.
I was considering implementing this same feature in my mod (Whistle Stop Factories) because I have map tags over each factory that indicates its current recipe. Currently I track a list of all of my factories and check them all on every nth tick.

If I were to capture these 3 events for creating these map tags, I'd miss recipe changes from recipes set by mods, but would I miss anything else? What do you mean by "the gui doesn't have to be explicitly closed by the player"? Are there situation in which the gui close event wouldn't be triggered?

I can't think of that many mods that set recipes. Crafting Combinator does, but I think it'd be fine to not support map tags for the rapidly changing recipes that mod produces. Are there any other mods that would cause a problem for me using this combination of triggers in place of the more ideal recipe_change?

Re: Event request: on_recipe_changed

Posted: Fri Oct 19, 2018 2:32 pm
by lovely_santa
Avacado wrote:
Fri Oct 19, 2018 2:36 am

If I were to capture these 3 events for creating these map tags, I'd miss recipe changes from recipes set by mods, but would I miss anything else? What do you mean by "the gui doesn't have to be explicitly closed by the player"? Are there situation in which the gui close event wouldn't be triggered?
Yes there are multiple examples, one of them when you move out of reach and the gui closes on you... or when you get disconnected from the game, ...

Re: Event request: on_recipe_changed

Posted: Fri Oct 19, 2018 3:28 pm
by eradicator
You could start a dynamic tick handler in on_gui_opened that continously watches player.opened until it is closed again. That way you don't need to care about the source of the window closing. And you only need to check at most $number_of_players or $number_of_connected_players per tick. And 0 when no factory guis are open.

Re: Event request: on_recipe_changed

Posted: Thu Mar 31, 2022 11:50 am
by Xorimuth
I'd also be interested in seeing this implemented, although the work-arounds suggested seem quite comprehensive.

Edit: I no longer need this

Re: Event request: on_recipe_changed

Posted: Thu Mar 31, 2022 4:58 pm
by Xorimuth
Xorimuth wrote:
Thu Mar 31, 2022 11:50 am
I'd also be interested in seeing this implemented, although the work-arounds suggested seem quite comprehensive.
So the main thing I've found impossible/very hacky to deal with is using blueprints on already-built assemblers to change their recipe. This only raises on_pre_built, which doesn't have enough information to reliably work out which recipes have been changed (particularly when it is a blueprint library blueprint which are invisible to the mod). :(