Boberts's Bwog

NTM's port problem

NTM has a bit of an issue with its ports. The underlying problem comes from the fact that there's really two competing port systems: One that lets tiles hook into UniNos, our network and transfer system, and one that allows tiles to access other tiles directly, which makes direct transfers possible. This dual system has caused a bunch of problems, made implementing special cases a nightmare, and worst of all, fixing it would require a lot of work since every single machine that uses a port in some way or another would need to be updated.

You thought this was gonna be about ported NTM versions, didn't you? Want that spicy Extended Edition drama? Them Community Editions? Lol, lmao even.

How did we get here

The reason why ports work like that is because ports are just a concept, it's what machines do to access a network, since machines and network aren't all that tighly linked. A network just exists, and it's the machine's job to find and connect to it.

To do this, machines would periodically (usually once per second or once per tick) check all port positions for all required network types (power, all relevant fluid tank types), check the block there, check if it's a "conductor", check its network, then subscribe to that network. This has to be done constantly, since subscriptions are timed, to ensure that tiles don't linger around in the network becoming "zombie tile entities" (an actual problem some prior systems had), a tile entity would be kicked out of the network after it has stopped subscribing for a short period of time (about 5 seconds). Here's another problem: For some fucking reason I decided to use the system time for this check instead of the world time, meaning that if you pause the game, all connections would time out, and there would be a short timespan where networks are inoperable. Not that big of a deal for machines that subscribe every tick, but noticeable for when that only happens every second. Like what was once the case for PWRs, for some fucking reason. I hope you can see the issue.

Another thing that would happen with those checks is for direct transfers to be done, if possible. If the port is connected to a block that's not a conductor, but rather a proper receiver, it would perform a direct transfer, allowing power and fluids to be shared even without requiring a network.

And why does this suck so much

The fundamental issue is, all these operations need to check in-world things, blocks or network nodes or whatever. For every type, for every port, constantly since time's running out on the subscriptions. This wasn't a problem initially (just like many bad ideas are), but it turns out that all this port-checking will waste a metric crapton of CPU time on servers with large setups (cough cough Gerver). It became so much of a problem that I finally decided to bite the bullet and redo the ports entirely.

Port attampt #1

Most machines already use a quasi-standard system where they had an array (i.e. an indexed fixed size list) of port definitions, which are plugged into some logic that performs subscriptions for all relevant types. So logically, the simplest way of reducing the amonut of port checks is by only doing them when we know that we need them. AutoPort was a system designed to perform checks with more delay depending on whether the previous operation succeeded or not. If there's already a connection, we can still afford to wait a few ticks, but we have to re-subscribe eventually in order to not cause a timeout. Statistically, connected ports are fewer in number than unconnected ports, so this should cause less of an issue. Unconnected ports only check slowly, since it doesn't happen often that a connection is added, and the delay while a setup is being built barely matters. If a machine is loaded for the first time due to chunk loading, we might want to check immediately, which sounds costly but it only happens once, after which one of the previous cases applies.

This system is incredibly easy, uses existing infrastructure and could handle types automagically by checking if sending or receiving tanks are available, instead of requiring those being defined for every machine manually. It was quick to implement, it sort of did what it was supposed to do and it made the code cleaner. AutoPort was great.

...until you realize it was just a band aid solution which doesn't even fix half the problems we already talked about. AutoPort was finished, and promptly incinerated.

Port attampt #2

It was finaly time to dig up an old idea, that being that ports themselves were network nodes, like how cables and pipes are. An older idea that I originally rejected because, if implemented by hand, would mean writing a ton of redundant code for something that might not even work the way I want it to. However, why write the same code 500+ times when you can write a small utility function that can create and manage ports automatically?

And that is the birth of PortDef. Like the original system with its position definitions, it uses a near identical format (just inset by one block), so converting the definitions from the old to the new system was easy, if a bit lengthy. Each tile now has a utility function for creating ports if they don't yet exist, and one for updating ports so that when a tank's fluid type changes, the port reflects that change. Instead of the port just being a concept, that of the tile accessing a network, the port is now an actual "thing" which hold a network node, which means the port can hook directly into the network. Since the tile holds direct references to its ports, and the ports hold nodes which are in a network, the tile can now access the network without having to check for connecting cables and pipes all the time.

Direct connections continue to work, since two machine's ports now connect the same way to cables would, so they form a shared network linked by their ports.

Finally something good

Since ports are now directly hooked into networks (for most machines at least, tanks and batteries in buffer mode have already done that for quite some time with a more manual approach), this means that ports can be connected internally with each other, similar to how substations have ports that are linked. This means that machines are now capable of passthrough, being able to share inputs and outputs between each other which used to require separate pipes. This feature is intentionally disabled for cables, though, since unlike fluids with its many types, a machine only needs a single cable anyway, and with cable connectors it's trivial to connect a bunch of machines within proximity.

The performance problem should be considered solved, since machines no longer need to do external checks for connections, and timeouts have been removed as well. The machine handles its ports and by extention network nodes, and therefore has to create and destroy nodes as needed. Since it already has to keep track of that, it can also simultaneously subscribe and unsubscribe from a network, solving that part and making timeouts obsolete.

< let-me-go / let-me-work