Modding API expansion for circuit network behavior.

Things that we aren't going to implement
Post Reply
alercah
Fast Inserter
Fast Inserter
Posts: 151
Joined: Sun Apr 07, 2019 5:19 am
Contact:

Modding API expansion for circuit network behavior.

Post by alercah »

TL;DR
Allow mods to exercise significantly more control over the circuit network, effectively allowing complete control over how entities and the circuit network interact.
What ?
Three major changes to the modding API and featureset of the game, which do not add a lot of overall complexity to the engine or core game, but expose a lot more of it to modders. Each change could be implemented independently, but I believe all three are needed to truly unlock maximum potential for modders. I've included a sample API spec (not necessarily complete) alongside each feature to enable more precise discussion, but I don't expect the final API would necessarily conform to this.
  1. Mods would be allowed to define custom wire types, to allow adding more overlapping circuit networks to the game. E.g. blue wires, purple wires, etc. A mod could define a wire type for internal use between entities (especially hidden entities) it creates, to allow a mod to essentially create private circuit networks.
    Sample specification
  2. Mods can directly add, modify, or remove connection points on entity prototypes. This would allow the mod to link entities to the network that otherwise wouldn't be able to.
    Sample specification
  3. Mods can specify the control behaviors of entities directly, and set some parameters. Existing control behaviors are made more granular so that this is more useful. Additionally, a new control behavior is added allowing a mod to control behavior directly.
    Sample specification
Possible issues ?
I don't know if it would be too much of a mess for backwards compatibility; I don't know how much it matters given that it would be a significant overhaul and the results would probably be majorly positive. Attempts to retain backwards compatibility, like with `circuit_connector_id`, could prove problematic. I've tried to write the sample spec in a way that respects performance costs, but I'm not the sort of player who builds factories for which UPS becomes a serious problem and I don't know the engine, so I don't know for sure.
Why ?
There have been frequent requests for more advanced behaviour be given to circuits, such as separating wire control or more machines connected and controlled. There are mods that give this advanced behaviour, but they frequently require using additional hidden entities with lots of scripting or constructing extra entities nearby.

The specific thing that sent me on my quest to trying to learn about modding and circuit networks was measuring flow through a pump, as a way to determine directly if the fluid supply to a train was too low (the workaround I'm currently using is measuring the last tank before the loading pumps; another one would be to compare values for the train on different ticks).

I think one of the major impediments to bringing this into the base game (forgive me if I'm wrong) is that there are so many parameters and so many options that it's hard to decide what to implement. This proposal is intended to open up this design space for modders, so that the best ideas can be coded, reviewed, and iterated in the community before inclusion in the core game.

Here's a list of things this enables or simplifies, which features are required for them, and how:
  1. (#2) Separating wire control can be done, even to some extent for pre-existing control behaviours, by hiding their existing connection point plugged into them, and replacing it with another connection point in the same position. The GUI is modified by the mod to allow selecting individual wire outputs for individual controls, and then script adjusts connections between the visible connection point and the hidden one accordingly. #3 is required to be able to separate behaviors onto different networks where one entity has effectively multiple.
  2. (#2, #3) New behaviors can be directly added to entities, even those that cannot currently be connected to the circuit network, by adding connections with appropriate behaviors. With #3 alone, existing behaviors can still be recombined without too much extra effort. Examples include toggling furnaces or assemblers or reading their contents, reading flow rate from pipes, etc.
  3. (#2, #3) New combinators can be created directly, such as a time-delay combinator, a selector (use one input to choose one of multiple outputs to send a second input through to), or a filter combinator, without needing multiple entities or excessively complex scripting.
  4. (#2, #3) Many mods like Shortwave and LTN can be made simpler by not requiring hidden entities, instead using multiple connection points on its stops.
Some design notes:
  1. The weird multi-position distribution thing for connection points is to allow modules that add wire types not have to worry about the positions they connect to, and modules that add power poles not to have to worry about new wire types, without creating an awful result. By having the game distribute them during loading, it makes it so that if a module adds blue and purple wires, the game will select one wire to go with red wires and one to go with green wires. The requirement of determinism makes it unlikely that you get a royal mess where wires criscross over different kinds of poles. An alternative would be to simply say that there are only three wire connection slots and each wire type has to pick one, but this way lets modders add more if they like.
  2. Built-in control behaviors are retained to avoid incurring performance cost and exposing proprietary source by moving them to the lua.
  3. The mod control behavior is distinct, as opposed to relying purely on events, to make exporting behaviors easier, to parameterize them, to increase performance by avoiding the need to test event filters for entities that have no custom behavior, and to avoid giant fights over the behavior-toggling GUI.
  4. Splitting existing built-in behaviors and allowing multiple per entity is done to make them more reusable and modular, and to create logical progression towards the creation of more built-in simple ones such as for reading parameters not currently exposed on the network. In particular, this allows modules to take advantage of existing C++ code to add existing behaviors to entities that do not have them. There would be many immediate improvements that could be considered
  5. I tried to avoid setups that would require the base game to add hidden connection points to things. For instance, read_logistic_contents would be enough to implement a logistic input to another connection by adding a hidden connection point and using that.
  6. Callbacks are used so that scripts cannot modify the values of signals directly or at weird times during processing, but only at the appropriate time in a tick.

Rseding91
Factorio Staff
Factorio Staff
Posts: 13209
Joined: Wed Jun 11, 2014 5:23 am
Contact:

Re: Modding API expansion for circuit network behavior.

Post by Rseding91 »

It sounds like you don't understand how the game is written/works internally. Everything is compiled - you can't runtime change how something works without editing the source code and recompiling the game to add support for it.

A super simple example which I did for 0.17.0 a few months ago: adding whitelist/blacklist support to filter inserters:

The total functionality of this feature: when in "whitelist" the inserter will grab anything which matches the filters it has set. When in "blacklist" it only grabs things which don't match the filters it has set.
  • Create a new enum (whitelist/blacklist) in the Inserter header file with values 0 and 1
  • Add the Whitelist property to the inserter class header file
  • Add a read function which returns the inserter whitelist mode to the inserter class header file
  • Add a write function which takes the new whitelist mode to the inserter class header file
  • Add a static parse-filter-mode function to the inserter class header file which takes a name and returns the whitelist/blacklist enum
  • Add a static parse-mode-to-string function to the inserter class header file which takes the whitelist enum and converts it to the string name
  • Add the map saving/loading of the whitelist value in the inserter cpp file with proper map version checking
  • Increment the map version for the new value the inserter is conditionally loading
  • Add the blueprint-string save/load of the whitelist value in the inserter cpp file
  • Add the write whitelist mode function to the cpp file and make sure if someone tries to set the mode to the current value it does nothing. Otherwise if the inserter doesn't support filters do nothing. Otherwise set the new mode and if the inserter is setup and isn't a ghost activate the inserter
  • Add the parse-filter-mode function to the inserter cpp file which converts a string into the whitelist enum or throws an error if the string isn't one of the whitelist or blacklist keywords
  • Add the filter-mode-to-string function to the inserter cpp file which converts the enum into the string name or aborts with the invalid enum value
  • Change the inserter function which checks if a given item matches the set filters to respect the current whitelist/blacklist enum value
  • Change the inserter copy-paste-settings function to also copy the filter mode value if the inserter uses filters
  • Change the inserter fast-replace function to also copy the filter mode value if the inserter uses filters
  • Add the InputAction handling for setting whitelist/blacklist on a given inserter to the GameActionHandler class
  • Add the GuiAction handling for setting whitelist/blacklist on a given inserter
  • Add the set-whitelist-blacklist handling logic to the CommonActionHandler class checking if the current opened entity is an inserter
  • Set the last user on the inserter when the whitelist/blacklist is changed by a player
  • Add a switch GUI element to the inserter GUI header file
  • Add the override function for switch-value-changed to the inserter GUI header file
  • Add the "whitelist" and "blacklist" locale entries
  • Populate the new switch GUI element in the inserter GUIs constructor with the 2 new locale entries
  • If the inserter supports filters add the switch GUI element to the GUI, set the switch state to the same as the inserters state, and add the event listener.
  • In the inserter GUI cpp file add the switch-value-changed function and if the source is the blacklist/whitelist switch GUI elemenet send the GUI action that the switch state was changed
  • Add the lua API read and write functions to the LuaEntity header file
  • Add the lua API read and write function bindings to the LuaEntity cpp file
  • Add the lua API read function in the LuaEntity cpp file with a description about what it can be used on, what it does, and the types it returns. If the LuaEntity isn't an inserter throw an error saying so. Otherwise check if the inserter supports filters and if not return nil otherwise convert the whitelist enum to a string and return that.
  • Add the lua API write function in the LuaEntity cpp file. If the LuaEntity isn't an inserter throw an error saying so. Otherwise read the input string from Lua, convert it into the whitelist/blacklist enum, and call the set-filter-mode function on the inserter.
All of that just for a switch in the inserter entity type. In total that commit changed 23 different files, added 161 lines of code and deleted 14.

To put it bluntly: what you propose simply will never happen. It would mean throwing out the entirety of the code around entities and starting over with the most generic system possible. Such a system would be so bloated with abstractions that nobody would be able to understand what it's actually doing and the performance would be so poor that you probably couldn't ever leave red science without it slowing down.
If you want to get ahold of me I'm almost always on Discord.

alercah
Fast Inserter
Fast Inserter
Posts: 151
Joined: Sun Apr 07, 2019 5:19 am
Contact:

Re: Modding API expansion for circuit network behavior.

Post by alercah »

I can accept that it's too much work to implement---it definitely would require some significant refactoring to make it work, based on my understanding of the existing architecture. But I'm surprised by your suggestion that it would involve completely rewriting the entity system and adding a lot of abstraction---are control behaviors not already a separate object (in C++)? I figured they were because of get_or_create_control_behavior().

That said, I've never seen the source, so you know lots I don't know and I'm not going to tell you that your codebase works a certain way. Only that I thought it maybe worked a different way.

(Incidentally, there are highly abstract systems like ECS that actually can perform quite well---so it's not a given that it would necessarily be slower. But changing fundamental architectures would be such a big project you might as well do nothing else for a few months!)

Post Reply

Return to “Won't implement”