Fluid physics (fix bizarreness, no extra calculation)
Moderator: ickputzdirwech
Fluid physics (fix bizarreness, no extra calculation)
Hello. I'm ashamed to say, that despite wanting very much to understand fluid physics in Factorio for a long time, I only just now discovered (or perhaps re-discovered, my memory is bad) these posts, which have informed me that "flow_to_energy_ratio" is about momentum, not conversion to electricity in engines:
viewtopic.php?f=18&t=33324#p209854
viewtopic.php?f=18&t=19851
XKnight devised this formula back in February of 2016 it looks like, and I wish I had learned it sooner.
My suggestion is, that it makes no sense to transfer fluids across each junction one at a time, and indeed this results in unintuitive effects such as flow rate being dependent on placement order, and streams at intersection seemingly cutting off others. So, instead of calculating a flow rate at a junction, then applying it, then doing the same for the next one, you could calculate all the flow rates then go back and apply them after after they are calculated. This requires iterating through the series twice, but really the amount of arithmetic is the same, so there shouldn't be any significant performance hit. Yet it would result in far, far more deterministic and intuitive fluid flows, with fewer quirks. Because, neither A] the placement order, nor B] the order of calculating flows between junctions, nor C] the order of applying those flows across the junctions, will influence the results, even for intersections and clumps of pipes and tanks.
viewtopic.php?f=208&t=8854&start=120#p163807
If you want an example of how this will affect the practical physics of the game, my simulation (which matched measured results perfectly) for a series of 176 pipes with an offshore pump at one end and nothing at the other, has this much water in the last pipe after 20 game seconds: 7.8417 water for pipes placed in same direction as flow, 6.8724 for pipes placed in opposite direction, 7.8947 if no flows are applied until after they are all calculated. This is 0.67% higher than correct direction, and 14.87% higher than the reverse placement.
viewtopic.php?f=18&t=33324#p209854
viewtopic.php?f=18&t=19851
XKnight devised this formula back in February of 2016 it looks like, and I wish I had learned it sooner.
My suggestion is, that it makes no sense to transfer fluids across each junction one at a time, and indeed this results in unintuitive effects such as flow rate being dependent on placement order, and streams at intersection seemingly cutting off others. So, instead of calculating a flow rate at a junction, then applying it, then doing the same for the next one, you could calculate all the flow rates then go back and apply them after after they are calculated. This requires iterating through the series twice, but really the amount of arithmetic is the same, so there shouldn't be any significant performance hit. Yet it would result in far, far more deterministic and intuitive fluid flows, with fewer quirks. Because, neither A] the placement order, nor B] the order of calculating flows between junctions, nor C] the order of applying those flows across the junctions, will influence the results, even for intersections and clumps of pipes and tanks.
viewtopic.php?f=208&t=8854&start=120#p163807
If you want an example of how this will affect the practical physics of the game, my simulation (which matched measured results perfectly) for a series of 176 pipes with an offshore pump at one end and nothing at the other, has this much water in the last pipe after 20 game seconds: 7.8417 water for pipes placed in same direction as flow, 6.8724 for pipes placed in opposite direction, 7.8947 if no flows are applied until after they are all calculated. This is 0.67% higher than correct direction, and 14.87% higher than the reverse placement.
Last edited by Aru on Tue Feb 28, 2017 8:49 pm, edited 1 time in total.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
In my opinion, if the code is to be touched, one should change it to make it right(tm).
That would mean one would have to incorporate preasure and momentum of the liquids which would make quite a lot of new problems computational wise.
And by the way: going through a list twice can take the double amount of time, even when doing the same amount of calculations due to caches! If you do two operations after each other, the data are already in the cache (or registers) for the second one, while you might have to reload the whole bank when you get there after traversing the whole array.
So while it might make sense to do it from a user perspective, computational wise it might not be good idea.
On the other hand some easy "fixes" might be possible by ordering and grouping Pipe segments like it is done with belts.
Personally I did not have had any problems with fluids or the throughput of pipes so far, so it isn't really an issue for me, though fixing implementation artifacts is always a good idea.
Stm
That would mean one would have to incorporate preasure and momentum of the liquids which would make quite a lot of new problems computational wise.
And by the way: going through a list twice can take the double amount of time, even when doing the same amount of calculations due to caches! If you do two operations after each other, the data are already in the cache (or registers) for the second one, while you might have to reload the whole bank when you get there after traversing the whole array.
So while it might make sense to do it from a user perspective, computational wise it might not be good idea.
On the other hand some easy "fixes" might be possible by ordering and grouping Pipe segments like it is done with belts.
Personally I did not have had any problems with fluids or the throughput of pipes so far, so it isn't really an issue for me, though fixing implementation artifacts is always a good idea.
Stm
Re: Fluid physics (fix bizarreness, no extra calculation)
With 0.15 a lot of the pipe physics seems to change. IMHO currently it's the wrong time to suggest useful changes.
Cool suggestion: Eatable MOUSE-pointers.
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
Have you used the Advanced Search today?
Need help, question? FAQ - Wiki - Forum help
I still like small signatures...
Re: Fluid physics (fix bizarreness, no extra calculation)
Well, after examining the formulas behind it, I think it already is pretty right. Honestly, I was rather impressed by the system. It's pretty cheap, and seems like it should produce very good results. Yet, the results are sometimes quite bad, and have a lot of quirks. The only difference is in the implementation. If they calculated all pipes, then applied the calculations, instead of calculating and applying for each pipe boundary one at a time, then everything would be nearly perfect. It would still be the same formula, and still essentially the same computational cost, but it would finally work the way it was supposed to in the first place. It almost seems like an oversight (please don't give me that "not a bug" stuff).
I was also always really, really impressed by the look and 'feel' of the fluid physics in this game, from the very first time I played it. I love it the way it is. The problems would be fixed by waiting until the calculations are done before applying them. Then, placement order wouldn't matter, and things would behave in a much more sane manner, the way one might expect the formula to behave in the first place.
@stm, the formula does primitively simulate both pressure and momentum. (Assuming that by 'pressure' you mean pressure at the bottom, i.e. volume, which is how it really works despite the internal use of the name 'pressure'. Perhaps they could update the pipe graphics to reflect the volume in addition to flow rate, similarly to fluid tanks, and correct any in-game mentions of 'pressure' to 'volume'.) You can watch the momentum in the pipes, it's satisfying to see. Each is implemented as arithmetically simply as it can be. The difference in fluid levels between pipes is factor #1 in flow rate, as well the rate of flow in the previous tick is factor #2. Each of these is easily calculated. This fact is EXTREMELY important for this game, there's so many things moving at a time that if it's not good enough on CPU time, there's no point in building big bases, which means the game would lose possibly it's most impressive and unique aspect. Much of the originality and 'wow' factor would be diminished. (That moment you realize that the game refuses to slow down, seemingly no matter how much you build. That is a moment of awe and inspiration, like you're just now realizing what the game is, and you want to play it all the more.)The other important thing, is that the fluid appears to flow in a realistic manner. Currently it does, except for the aforementioned problem of updating game state before the calculations are complete. The combination of satisfying flows and scalability to large bases is what makes it so good. But applying the calculations before they are all completed is what makes it bad. It can be readily fixed. The thing which is fundamentally critical, is properly conserving the fluids during these calculations and allowing them to flow freely, which it does, so no problems there.
(I was playing "oxygen not included" yesterday, which is very buggy and "pre-alpha", the fluid physics at the moment are ATROCIOUS. When I fed water into an existing pool, the level actually went down, and it splashed around unrealistically for a long time. Water is very precious in that game, so, it's bad. In other words, it does not matter how unrealistic and unsatisfying the motion is, materials disappearing cannot be allowed to happen, somehow that violates intuitive physical principles more than anything else possibly can. Materials turn from meaningful virtual substances into fickle computer numbers, which makes it feel like you're playing the computer rather than playing the game. And the game is the whole selling point. Interface limitations which could be fixed simply are similarly violating.)
I was also always really, really impressed by the look and 'feel' of the fluid physics in this game, from the very first time I played it. I love it the way it is. The problems would be fixed by waiting until the calculations are done before applying them. Then, placement order wouldn't matter, and things would behave in a much more sane manner, the way one might expect the formula to behave in the first place.
@stm, the formula does primitively simulate both pressure and momentum. (Assuming that by 'pressure' you mean pressure at the bottom, i.e. volume, which is how it really works despite the internal use of the name 'pressure'. Perhaps they could update the pipe graphics to reflect the volume in addition to flow rate, similarly to fluid tanks, and correct any in-game mentions of 'pressure' to 'volume'.) You can watch the momentum in the pipes, it's satisfying to see. Each is implemented as arithmetically simply as it can be. The difference in fluid levels between pipes is factor #1 in flow rate, as well the rate of flow in the previous tick is factor #2. Each of these is easily calculated. This fact is EXTREMELY important for this game, there's so many things moving at a time that if it's not good enough on CPU time, there's no point in building big bases, which means the game would lose possibly it's most impressive and unique aspect. Much of the originality and 'wow' factor would be diminished. (That moment you realize that the game refuses to slow down, seemingly no matter how much you build. That is a moment of awe and inspiration, like you're just now realizing what the game is, and you want to play it all the more.)The other important thing, is that the fluid appears to flow in a realistic manner. Currently it does, except for the aforementioned problem of updating game state before the calculations are complete. The combination of satisfying flows and scalability to large bases is what makes it so good. But applying the calculations before they are all completed is what makes it bad. It can be readily fixed. The thing which is fundamentally critical, is properly conserving the fluids during these calculations and allowing them to flow freely, which it does, so no problems there.
(I was playing "oxygen not included" yesterday, which is very buggy and "pre-alpha", the fluid physics at the moment are ATROCIOUS. When I fed water into an existing pool, the level actually went down, and it splashed around unrealistically for a long time. Water is very precious in that game, so, it's bad. In other words, it does not matter how unrealistic and unsatisfying the motion is, materials disappearing cannot be allowed to happen, somehow that violates intuitive physical principles more than anything else possibly can. Materials turn from meaningful virtual substances into fickle computer numbers, which makes it feel like you're playing the computer rather than playing the game. And the game is the whole selling point. Interface limitations which could be fixed simply are similarly violating.)
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
No it wouldn't. Touching every pipe twice would be twice as expensive.Aru wrote:Well, after examining the formulas behind it, I think it already is pretty right. Honestly, I was rather impressed by the system. It's pretty cheap, and seems like it should produce very good results. Yet, the results are sometimes quite bad, and have a lot of quirks. The only difference is in the implementation. If they calculated all pipes, then applied the calculations, instead of calculating and applying for each pipe boundary one at a time, then everything would be nearly perfect. It would still be the same formula, and still essentially the same computational cost, but it would finally work the way it was supposed to in the first place. It almost seems like an oversight (please don't give me that "not a bug" stuff).
If you want to get ahold of me I'm almost always on Discord.
Re: Fluid physics (fix bizarreness, no extra calculation)
Fluid level is not the same as preasure!Aru wrote: @stm, the formula does primitively simulate both pressure and momentum. (Assuming that by 'pressure' you mean pressure at the bottom, i.e. volume, which is how it really works despite the internal use of the name 'pressure'. Perhaps they could update the pipe graphics to reflect the volume in addition to flow rate, similarly to fluid tanks, and correct any in-game mentions of 'pressure' to 'volume'.) You can watch the momentum in the pipes, it's satisfying to see. Each is implemented as arithmetically simply as it can be. The difference in fluid levels between pipes is factor #1 in flow rate, as well the rate of flow in the previous tick is factor #2. Each of these is easily calculated. This fact is EXTREMELY important for this game, there's so many things moving at a time that if it's not good enough on CPU time, there's no point in building big bases, which means the game would lose possibly it's most impressive and unique aspect. Much of the originality and 'wow' factor would be diminished. (That moment you realize that the game refuses to slow down, seemingly no matter how much you build. That is a moment of awe and inspiration, like you're just now realizing what the game is, and you want to play it all the more.)The other important thing, is that the fluid appears to flow in a realistic manner. Currently it does, except for the aforementioned problem of updating game state before the calculations are complete. The combination of satisfying flows and scalability to large bases is what makes it so good. But applying the calculations before they are all completed is what makes it bad. It can be readily fixed. The thing which is fundamentally critical, is properly conserving the fluids during these calculations and allowing them to flow freely, which it does, so no problems there.
The Problem is, that Preasure is linked with momentum. Even neglecting things like turbulences and friction, you have to take into account at least [url="https://en.wikipedia.org/wiki/Bernoulli%27s_principle]Bernoulli's principle[/url], impact preasure (hope that is the right term here) and conversion of momentum (what happens at T sections).
But even if only preasure would be implemented correctly things like the directional bias from
viewtopic.php?f=208&t=8854&start=120#p163807
would not be possible.
But as I already mentioned: all that would be a computational nightmare.
Not that I have played the mod or trying to defend its imlementation, but oscillations at least are a quite normal in the real world. And spilling due to fluid insertion can happen as well (ever completely opened a faucet without a preasure reducer at a sink?)Aru wrote: (I was playing "oxygen not included" yesterday, which is very buggy and "pre-alpha", the fluid physics at the moment are ATROCIOUS. When I fed water into an existing pool, the level actually went down, and it splashed around unrealistically for a long time. Water is very precious in that game, so, it's bad. In other words, it does not matter how unrealistic and unsatisfying the motion is, materials disappearing cannot be allowed to happen, somehow that violates intuitive physical principles more than anything else possibly can. Materials turn from meaningful virtual substances into fickle computer numbers, which makes it feel like you're playing the computer rather than playing the game. And the game is the whole selling point. Interface limitations which could be fixed simply are similarly violating.)
Stm
Re: Fluid physics (fix bizarreness, no extra calculation)
At least, the arithmetic would be the same. The change is calculated once, and applied once, just the same. Except, each calculation is made using the unchanged values, then the levels are each updated. Then all of the strangeness of the fluid physics would be gone, and it would be perfect. Each fluid level is still read only once, still +='d only once, and the arithmetic is entirely identical, except it would work right. It would not even require extra assignments or memory, because the flows of the previous cycle are already being saved for the momentum component anyway. Since there is some floating point arithmetic involved, I don't think doubling the overhead of looping through the pipe joints is that extreme in comparison (game is c++), whereas it would have to be literally everything to double the cost in CPU time. Perhaps they would have done it like this in the first place if they knew in advance that there was a not-insignificant difference between them. Doing it like this would also open up the possibility of multi-threading the fluid physics (because the iteration through pipe joints would no longer depend on the results of the previous joint) which would increase performance many times over, but it's probably not worth the effort. (You could probably even do it as shader code.) Perhaps then, it might perform better simply from compile/run time optimizations, as it becomes open to parallelization.Rseding91 wrote:No it wouldn't. Touching every pipe twice would be twice as expensive.
stm wrote:Fluid level is not the same as preasure!
The Problem is, that Preasure is linked with momentum. Even neglecting things like turbulences and friction, you have to take into account at least [url="https://en.wikipedia.org/wiki/Bernoulli%27s_principle]Bernoulli's principle[/url], impact preasure (hope that is the right term here) and conversion of momentum (what happens at T sections).
But even if only preasure would be implemented correctly things like the directional bias from
viewtopic.php?f=208&t=8854&start=120#p163807
would not be possible.
But as I already mentioned: all that would be a computational nightmare.
What I meant was, the pipes work like open troughs that liquids (and gases apparently, they act like liquids) just flow through. (So, Bernoulli's principle isn't relevant. My suggestion was just a way to remove all the unrealistic, broken oddities of the current formula, without even changing it.) The indicated values are really the volume of fluid in each pipe segment, yet it's termed pressure. The only connection I could make, is that pressure at the bottom is proportionate to the height of the fluid above it (plus atmospheric pressure), which is roughly related to volume, though it depends on the shape of the pipe. Just as pressure goes up as you descend into water, from gravity. It's not a strong connection, which was my point, it was the strongest I could make. The info box numbers really indicate volume. Also, the directional bias shown in the link would disappear if, as I describe, the updates are not applied until after the changes are calculated.Aru wrote:@stm, the formula does primitively simulate both pressure and momentum. (Assuming that by 'pressure' you mean pressure at the bottom, i.e. volume, which is how it really works despite the internal use of the name 'pressure'. Perhaps they could update the pipe graphics to reflect the volume in addition to flow rate, similarly to fluid tanks, and correct any in-game mentions of 'pressure' to 'volume'.)
The mention of the other game was a total aside, just to emphasize how important it is that resources don't mysteriously disappear, even in a fluid physics system. Currently it does not (except for picking up buildings), but it happens in other games, and I just mentioned it because I'd like it to stay that way.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
This is exactly why I included the numbers that I did in my original post, to show that the change in throughput is not too dramatic, therefore does not require rebalancing or tweaking the coefficients (0.40 from current fluid difference and 0.59 from the previous flow). The whole point is to fix the vast array of incomprehensible implementation artifacts. Why is it that one pipe often has more fluid than the pipes on either side of it, even in a standing flow? And many other oddities and, even worse, inconsistencies, which make absolutely no physical sense, and makes it seem like the whole system is rather broken, makes the simulation seem fundamentally flawed. When really I think most (or all) of them are caused by applying each flow calculation before starting the next one, instead of after they are done. I think if the devs tried this change, optimized and benchmarked it, even without explicitly making it more parallel (which this change absolutely enables), you'd find that the performance hit is minimal, if any. It would not take twice as long. Even with thousands of pipes and underground pipes, you would not come close to filling a typical L1 cache, let alone L3 (4-byte float * 2 per segment). This would completely fix the fluid physics, without altering the formula.stm wrote:Personally I did not have had any problems with fluids or the throughput of pipes so far, so it isn't really an issue for me, though fixing implementation artifacts is always a good idea.
I feel like the best, simplest, easiest and most thought-out suggestions are often brushed away in under 20 words by a purple account who thinks it's not good enough to be worth more than 60 seconds of consideration. Then it's never seen or considered again by staff. Then they come to the same conclusion 3 years later, and THEN implement it. Such a waste. Imagine if all those improvements were not ignored, and instead applied earlier, how much more time the game would have spent in a better state, how much more impact it would have had on public perception.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
The cost of simulating pipes is going over and touching each pipe - cache misses as each pipe tries to flow to adjacent pipes all scattered randomly in memory. So, iterating them all twice would make it twice as expensive.Aru wrote:At least, the arithmetic would be the same. The change is calculated once, and applied once, just the same. Except, each calculation is made using the unchanged values, then the levels are each updated. Then all of the strangeness of the fluid physics would be gone, and it would be perfect. Each fluid level is still read only once, still +='d only once, and the arithmetic is entirely identical, except it would work right. It would not even require extra assignments or memory, because the flows of the previous cycle are already being saved for the momentum component anyway. Since there is some floating point arithmetic involved, I don't think doubling the overhead of looping through the pipe joints is that extreme in comparison (game is c++), whereas it would have to be literally everything to double the cost in CPU time. Perhaps they would have done it like this in the first place if they knew in advance that there was a not-insignificant difference between them. Doing it like this would also open up the possibility of multi-threading the fluid physics (because the iteration through pipe joints would no longer depend on the results of the previous joint) which would increase performance many times over, but it's probably not worth the effort. (You could probably even do it as shader code.) Perhaps then, it might perform better simply from compile/run time optimizations, as it becomes open to parallelization.Rseding91 wrote:No it wouldn't. Touching every pipe twice would be twice as expensive.
If you want to get ahold of me I'm almost always on Discord.
Re: Fluid physics (fix bizarreness, no extra calculation)
Even if the second iteration is immediately after the first, and it all fits in L1 quite comfortably? Maybe I don't understand this well, but it shouldn't be missing if the first pass already loaded them.Rseding91 wrote:The cost of simulating pipes is going over and touching each pipe - cache misses as each pipe tries to flow to adjacent pipes all scattered randomly in memory. So, iterating them all twice would make it twice as expensive.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
What about a factory with more pipes in it than bytes fit in L1 cache.Aru wrote:Even if the second iteration is immediately after the first, and it all fits in L1 quite comfortably? Maybe I don't understand this well, but it shouldn't be missing if the first pass already loaded them.Rseding91 wrote:The cost of simulating pipes is going over and touching each pipe - cache misses as each pipe tries to flow to adjacent pipes all scattered randomly in memory. So, iterating them all twice would make it twice as expensive.
BTW, wouldn't it be possible to stagger the two steps between two sequential updates? Instead of running thru all the pipes twice in a single update, buffer the result of the flow calculation in one update then apply it on the next update just before making the next flow calculation.
I think this should have the same effect as doing all the calculations first and then applying the effects all at once. But pipes would still only be touched once per update. The downside is that there would be a one update delay in fluid simulation.
Re: Fluid physics (fix bizarreness, no extra calculation)
I do appreciate the responses, was just a suggestion.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
A more thought-out version of the suggestion, including the one-pipe delay as mentioned by Yoyobuae in the post above, so that the vast majority of the calculations are done in one pass, as they are currently (parallelization would still be much faster, though):
One vector (edit: for non-programmer people, a vector is a one-dimensional array, in this example a series of pipe segments) for segments at 3- and 4-way intersections, corresponding to scattered positions on the map. This is only a small minority of the pipes laid, and this minority becomes (proportionately) smaller as the fluid system gets larger. And a vector for every series of pipes which are each only connected at 2 sides (even if it's only 1 segment long), these are of course ordered from one end to the other.
Iterate through the serial (pipeline) vectors, keeping the flow calculation exactly one joint ahead of the volume calculation. Again, this uses the same existing formula, and involves no additional calculation or assignment (possibly even fewer, depending how the current system works exactly). And only one pass, so guaranteed no cache misses (prefetch), except when switching to the next vector (pipeline). For each pipe joint, calculate the flow, and then for each pipe segment, sum the adjacent flows and update the volume and corresponding graphic (AFTER those flows have been updated from the volumes of the PREVIOUS tick). (I'm guessing that this graphic is calculated from the difference of the two flows, in which case this is the perfect time to calculate it. Also graphics for isolated single segments with fluid in them, though they need no physics updates.) If the series ends in an intersection, don't update the volume of the intersection pipe yet, wait until the rest of it's flows are calculated.
Once those are done, go back and update the volumes of the intersection pipes. Here, you retrieve the flow calculations from the ends (or beginnings) of each of the serial vectors which connect to the intersection. If any flow calculations are absent, calculate those. (This can only be the case when you have 4 pipes in a 2x2 square, and additionally two adjacent pipes out of the four must have another connection, really how often does this happen in normal play.)
You also have to re-do parts of the system of vectors whenever they are modified in-game. But at these moments, the player is not doing anything else intensive (because they are messing with the fluid system). As this only happens when the system changes, it is not performed in the main loop, thus does not continually suck CPU time. If you want me to come up with somewhat optimized details for this part too, I probably can, but I can't do much more without knowing much about how the code currently works. Given a framework, I could make a meaningful, profile-able, optimized mock-up of the whole system, but I'd need to know things like (roughly) how it interacts with graphics. In any case, in an especially large base where cost of this (updating of the fluid system) might matter, truly you would only have to alter the structure of a small portion of the greater system. (Again, this portion is relatively smaller the larger the base is. The purpose of this updating is to split, reduce, make, delete, otherwise modify vectors as needed to ensure it still fits the description above.) You can also check occasionally to see if a network is isolated from active producers and consumers of fluid, in which case it no longer has to be updated once the levels are relatively settled. Any artifacts created from this jump (from being updated to being static) would be imperceptibly small as the fluids are all settled. The cheapest way to be certain that the levels are settled, is to consider how long ago it became isolated, and also how large it is, then just freeze it until it is expanded. (In my games, there are not many of these inactive isolated fluid networks at a time, but I imagine they become more prevalent in very large bases where the player has resources and doesn't bother to pick up unused lines, for whatever reasons.)
There, now the calculations are correct, the fluid mechanics work the way the formula says it's supposed to work, all of the bizarre implementation artifacts are gone, and 95%+ (probably 99%+ in any base of medium or greater size) of the pipes are being updated (that is, read, calculated and compound-assigned) as sequential arrays with only one pass, without any added reads, arithmetic, or assignments.
edit: minor fixes and clarity
One vector (edit: for non-programmer people, a vector is a one-dimensional array, in this example a series of pipe segments) for segments at 3- and 4-way intersections, corresponding to scattered positions on the map. This is only a small minority of the pipes laid, and this minority becomes (proportionately) smaller as the fluid system gets larger. And a vector for every series of pipes which are each only connected at 2 sides (even if it's only 1 segment long), these are of course ordered from one end to the other.
Iterate through the serial (pipeline) vectors, keeping the flow calculation exactly one joint ahead of the volume calculation. Again, this uses the same existing formula, and involves no additional calculation or assignment (possibly even fewer, depending how the current system works exactly). And only one pass, so guaranteed no cache misses (prefetch), except when switching to the next vector (pipeline). For each pipe joint, calculate the flow, and then for each pipe segment, sum the adjacent flows and update the volume and corresponding graphic (AFTER those flows have been updated from the volumes of the PREVIOUS tick). (I'm guessing that this graphic is calculated from the difference of the two flows, in which case this is the perfect time to calculate it. Also graphics for isolated single segments with fluid in them, though they need no physics updates.) If the series ends in an intersection, don't update the volume of the intersection pipe yet, wait until the rest of it's flows are calculated.
Once those are done, go back and update the volumes of the intersection pipes. Here, you retrieve the flow calculations from the ends (or beginnings) of each of the serial vectors which connect to the intersection. If any flow calculations are absent, calculate those. (This can only be the case when you have 4 pipes in a 2x2 square, and additionally two adjacent pipes out of the four must have another connection, really how often does this happen in normal play.)
You also have to re-do parts of the system of vectors whenever they are modified in-game. But at these moments, the player is not doing anything else intensive (because they are messing with the fluid system). As this only happens when the system changes, it is not performed in the main loop, thus does not continually suck CPU time. If you want me to come up with somewhat optimized details for this part too, I probably can, but I can't do much more without knowing much about how the code currently works. Given a framework, I could make a meaningful, profile-able, optimized mock-up of the whole system, but I'd need to know things like (roughly) how it interacts with graphics. In any case, in an especially large base where cost of this (updating of the fluid system) might matter, truly you would only have to alter the structure of a small portion of the greater system. (Again, this portion is relatively smaller the larger the base is. The purpose of this updating is to split, reduce, make, delete, otherwise modify vectors as needed to ensure it still fits the description above.) You can also check occasionally to see if a network is isolated from active producers and consumers of fluid, in which case it no longer has to be updated once the levels are relatively settled. Any artifacts created from this jump (from being updated to being static) would be imperceptibly small as the fluids are all settled. The cheapest way to be certain that the levels are settled, is to consider how long ago it became isolated, and also how large it is, then just freeze it until it is expanded. (In my games, there are not many of these inactive isolated fluid networks at a time, but I imagine they become more prevalent in very large bases where the player has resources and doesn't bother to pick up unused lines, for whatever reasons.)
There, now the calculations are correct, the fluid mechanics work the way the formula says it's supposed to work, all of the bizarre implementation artifacts are gone, and 95%+ (probably 99%+ in any base of medium or greater size) of the pipes are being updated (that is, read, calculated and compound-assigned) as sequential arrays with only one pass, without any added reads, arithmetic, or assignments.
edit: minor fixes and clarity
Last edited by Aru on Mon May 08, 2017 11:55 am, edited 13 times in total.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
I have spent some time trying to decipher how fluid boxes transfer liquid ( viewtopic.php?f=18&t=33685 - open up the pseudocode spoiler) and I have noted some quirks with the behavior of liquids while playing. But, I've played other games where the "realistic" fluid mechanics slowed the framerate of the game so much that it ruined the experience (dwarf fortress and gnomoria come to mind). Considering the large scale of some gamemaps and the amount of processing these games already handle, I'm all for relaxing the behavior of liquids.
You say it shouldn't add overhead to the processing, but I'm not going to believe you until I see it tested in code. Already, each pipe tile iterates through a directions vector list, remembers the previous flowrate "flowenergy" for each direction and does a simple pressure calculation for each direction. The results are good for the game and we keep a decent framerate even for some monstrous game map sizes. There might be a better way, but it'll be a challenge to figure it out.
You say it shouldn't add overhead to the processing, but I'm not going to believe you until I see it tested in code. Already, each pipe tile iterates through a directions vector list, remembers the previous flowrate "flowenergy" for each direction and does a simple pressure calculation for each direction. The results are good for the game and we keep a decent framerate even for some monstrous game map sizes. There might be a better way, but it'll be a challenge to figure it out.
Re: Fluid physics (fix bizarreness, no extra calculation)
Well doing it as described in the most recent post, without parallelizing, will probably add some overhead, but for any real base that overhead will scale as an exponent that is much less than 1 (in other words, less than linear, which is less than exponential). Depending how it's done currently, depending what rseding meant by things being scattered in memory, it might even be faster, but I don't know how the relevant code is currently structured. Assuming that the current implementation could be much faster, that faster aspect is separate from the part of my suggestion which corrects the implementation of the fluid physics; in other words if my last example is somehow faster than the current system, it would be very slightly faster still if it left out the physics fix. Fixing the physics (without parallelization) only adds overhead (I don't know if that makes much sense, hard to say). But assuming that the system is already optimized somewhat as described (I have no idea how it is structured), the additional CPU hit should be minimal and scale minimally. If it's not done this way at all, then depending how it interacts with the rest of the game (such as graphics), it's maybe possible to make it considerably faster just by optimizing the current implementation of the fluid formula.DerivePi wrote: ... Considering the large scale of some gamemaps and the amount of processing these games already handle, I'm all for relaxing the behavior of liquids.
You say it shouldn't add overhead to the processing, but I'm not going to believe you until I see it tested in code. ...
(edit clarity)
edit: I'm sure there's also things that can be done to make long sequences of pipes which are off-screen vastly faster without noticeably disrupting the feel of the overall physics, but this would kind of be another suggestion built on top of the current 1 to 2 suggestions (1 or 2 depending how it's done now). I will be thinking about this.
Also, after reading your post again, this suggestion doesn't alter the underlying fluid physics formula at all, it doesn't even create any new arithmetic or reads or assignments. Depending how it's done now, it might even do fewer. The advantage is an implementation that is consistent, without oddities, and behaves the way you'd expect it to, without the inexplicable oddities such as throughput depending whether pipes are placed in order or reverse order of the flow, and fluid buildups in "bumps" in the lines, I'm sure many other things too. The downside is that it adds overhead only via potential cache misses. So by adding complexity to the system as described above, I think the added cost could potentially be minimal. If long off-screen pipes were optimized (this is a totally separate idea), it may slightly alter the physics but could eliminate most of the core fluid physics calculations done in large bases, which could reduce the cost of the entire fluid system dramatically (for large bases, which is where it counts).
Last edited by Aru on Thu Mar 09, 2017 10:58 pm, edited 9 times in total.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
How about going through the pipes randomly? Then any fluctuations should even out eventually. Although if they are adjacent in memory then going through them sequentially could be faster I suppose, but in that case you may still want to consider adding some degree of randomness, to avoid creating weird feedback loops.Rseding91 wrote: The cost of simulating pipes is going over and touching each pipe - cache misses as each pipe tries to flow to adjacent pipes all scattered randomly in memory. So, iterating them all twice would make it twice as expensive.
Re: Fluid physics (fix bizarreness, no extra calculation)
That would work too, to smooth things out, but only if the game lacks some kind of sequential optimization (it might, I don't know, rseding did mention "scattered" pipes, but maybe this is necessary to correlate them with graphics anyway?). If things are not in sequence, then this would smooth things out only with the added cost of an especially fast RNG.Shiandow wrote:How about going through the pipes randomly? Then any fluctuations should even out eventually. Although if they are adjacent in memory then going through them sequentially could be faster I suppose, but in that case you may still want to consider adding some degree of randomness, to avoid creating weird feedback loops.Rseding91 wrote: The cost of simulating pipes is going over and touching each pipe - cache misses as each pipe tries to flow to adjacent pipes all scattered randomly in memory. So, iterating them all twice would make it twice as expensive.
Designs: v0.16 | Automated nuclear | Centrifuge ratios | Solar + Accumulator
Re: Fluid physics (fix bizarreness, no extra calculation)
Been thinking about this. The current (minor) problem, as I understand it, is that the fluid in the pipe is distributed to adjacent pipe connections on a first come first serve basis. On the update() function - the FluidBox iterates through a standard vector list of Connections which I imagine is appended based on construction order (ex. if you build a pipe line West to East the pipe connection to the west precedes the east connection in the vector list for that fluidbox). The update() function does a pressure calculation between the current FluidBox and the adjacent fluidbox(s) it *points to. It then transfers the calculated amount of fluid to the other fluid box and then iterates through the next connection on the list.
If the demand for fluid from other boxes exceeds the available fluid, the first connection on the list gets preference without considering the demand from other connections.
One way to adjust this would be to:
- Create a local vector of valid connection pointers and a variable double netDemand. (definitely some overhead here)
- Iterate through the Connection list and store the valid connections into the local vector
- Calculate the pressures for each demanding connection and add them to netDemand. (I don't see any extra overhead here - function already does this)
- If netDemand exceeds amount, apply a coefficient = (amount / netDemand) to the transferFluid() function for each valid connection. (this adds one more calculation for each pipe connection)
I don't think this will address issues that arise due to the order the Fluidboxes are addressed. For instance, if the game updates fluidboxes from west to east then any flow from the west to the east will occur in line with the order so flow from the west will already be in the next east box when it is scanned. When flowing against the scan order, the fluidbox will not have the adjacent fluid already accounted for. To me, this is even a more minor issue that disappears after a couple of game ticks and is mostly unnoticeable without measurement.
If the demand for fluid from other boxes exceeds the available fluid, the first connection on the list gets preference without considering the demand from other connections.
One way to adjust this would be to:
- Create a local vector of valid connection pointers and a variable double netDemand. (definitely some overhead here)
- Iterate through the Connection list and store the valid connections into the local vector
- Calculate the pressures for each demanding connection and add them to netDemand. (I don't see any extra overhead here - function already does this)
- If netDemand exceeds amount, apply a coefficient = (amount / netDemand) to the transferFluid() function for each valid connection. (this adds one more calculation for each pipe connection)
I don't think this will address issues that arise due to the order the Fluidboxes are addressed. For instance, if the game updates fluidboxes from west to east then any flow from the west to the east will occur in line with the order so flow from the west will already be in the next east box when it is scanned. When flowing against the scan order, the fluidbox will not have the adjacent fluid already accounted for. To me, this is even a more minor issue that disappears after a couple of game ticks and is mostly unnoticeable without measurement.
Re: Fluid physics (fix bizarreness, no extra calculation)
From my experience the proper way to fix this kind of a problem is to just make the value of current fluidlevel an array of size 2 and then when you read the value use value[gametick mod 2] and when writing use value[gametick+1 mod 2]. Also known as the pingpong method. You use a bit more memory but its really simple to implement and the preformance impact is usually rather low. Ofcourse this may not solve all the problems and you may need to change some calculations but it's really the only way to go if you wan't the result be independent from the order in wich the pipes are accessed.
Re: Fluid physics (fix bizarreness, no extra calculation)
I understand that 0.15 will bring new stuff on pipe mechanics but some things will still remain I think:
As of right now, whenever you drop a fluid in a pipe section the fluid will try to spread evenly across the entire pipe section (okay mister we all know that...) but from what I think its happening is that each pipe entity will get its content and toss it on each ending with an Integer rounding algorithm so that [fluid_count_val div 2] produces some proper values?
(you can jump to the last paragraph to just read my idea)
Just to make an example of what I think its happening with pipes so they have this weird behavior:
Imagine we have a pipe section with 100 units of fluid in it, this section is represented by :=:
now we will connect a new pipe to this section:
:=::=:
"The fluid will flow from the first pipe to the second pipe untill the ammount of fluid is even on both, a.k.a 50 on each" okay, lets simulate:
Since each pipe section transports 10u of fluid per gametick, it should take 5 gameticks to get from 100 to 50 right?
That probably would work with just 2 pipes connected, but what happens if we link a 3rd one?
From my testings and simulations, each pipe will try to push its content to all the other pipes untill the sum of all of them equals the initial value, example:
:=::=::=:
1 2 3
Pipe #1 has 100u of fluid, the final scenario would be each pipe having 33.3u of fluid or 33.3, 33.3, 33.4.
Theorycraft:
[1] Fluid on pipe 2 is 15 because on a previous tick it had 10 units while its connected right side had 0 and connected left side had greater_than_self value, so it pushed half of its content to right side so the sum of both pipes equals the initial value of 10u.
[2] Same deal as previous but this time the pipe #2 had 15u of fluid so 7.5u got transferred
Around the 9th iteration the fluid count on all pipes will mostly look somewhat even due to rounding of float values
(okay mister, your point?)
Now if we have a machine that consumes 10u of fluid per tick connected on the 3rd pipe, the machine will have an efficiency of 50% since there's lesser fluid on the pipe per tick than what the machine requires.
If we attach a machine on 1st pipe consuming 10u per tick, the other machine on pipe 3 will never work unless the first one stops consuming the fluid on pipe 1, even if we apply a pump feeding 15u to pipe 1.
(you're wrong, pipes can transport 150u of fluids) per second, not per gametick!
*Machines that consumes 10u per tick or 15u per tick can be acheived with bob's mods modules and MK3 beacons to make these simulations possible.
So, after all this wall of text, my idea is: Instead of reading/set each pipe of a section individually, count the whole section as 1 big pipe, so the fluid that gets pumped in through 1 side will apply enough pressure to spread the fluid to all the pipes in the section at same tick, or maybe read the content of the input side to ensure that every pipe on the section will transport MAX_FLOW_RATE instead of evening themselves if the content of the input is greater than MAX_FLOW_RATE
As of right now, whenever you drop a fluid in a pipe section the fluid will try to spread evenly across the entire pipe section (okay mister we all know that...) but from what I think its happening is that each pipe entity will get its content and toss it on each ending with an Integer rounding algorithm so that [fluid_count_val div 2] produces some proper values?
(you can jump to the last paragraph to just read my idea)
Just to make an example of what I think its happening with pipes so they have this weird behavior:
Imagine we have a pipe section with 100 units of fluid in it, this section is represented by :=:
now we will connect a new pipe to this section:
:=::=:
"The fluid will flow from the first pipe to the second pipe untill the ammount of fluid is even on both, a.k.a 50 on each" okay, lets simulate:
Since each pipe section transports 10u of fluid per gametick, it should take 5 gameticks to get from 100 to 50 right?
Code: Select all
Iteraction | fluid count on pipe 1 | fluid count on pipe 2
1 100 0
2 90 10
3 80 20
4 70 30
5 60 40
6 50 50
From my testings and simulations, each pipe will try to push its content to all the other pipes untill the sum of all of them equals the initial value, example:
:=::=::=:
1 2 3
Pipe #1 has 100u of fluid, the final scenario would be each pipe having 33.3u of fluid or 33.3, 33.3, 33.4.
Theorycraft:
Code: Select all
Iteraction | fluid count on pipe 1 | fluid count on pipe 2 | fluid count on pipe 3
1 100 0 0
2 90 10 0
3[1] 80 15 5
4[2] 70 17.5 12.5
5 60 25 15
6 50 30 20
7 40 35 25
8 37.5 32.5 30
9 35 33.25 31.25
10 34.125 33.122 32.25
11 33.6235 33.1875 32.686
...
[2] Same deal as previous but this time the pipe #2 had 15u of fluid so 7.5u got transferred
Around the 9th iteration the fluid count on all pipes will mostly look somewhat even due to rounding of float values
(okay mister, your point?)
Now if we have a machine that consumes 10u of fluid per tick connected on the 3rd pipe, the machine will have an efficiency of 50% since there's lesser fluid on the pipe per tick than what the machine requires.
If we attach a machine on 1st pipe consuming 10u per tick, the other machine on pipe 3 will never work unless the first one stops consuming the fluid on pipe 1, even if we apply a pump feeding 15u to pipe 1.
(you're wrong, pipes can transport 150u of fluids) per second, not per gametick!
*Machines that consumes 10u per tick or 15u per tick can be acheived with bob's mods modules and MK3 beacons to make these simulations possible.
So, after all this wall of text, my idea is: Instead of reading/set each pipe of a section individually, count the whole section as 1 big pipe, so the fluid that gets pumped in through 1 side will apply enough pressure to spread the fluid to all the pipes in the section at same tick, or maybe read the content of the input side to ensure that every pipe on the section will transport MAX_FLOW_RATE instead of evening themselves if the content of the input is greater than MAX_FLOW_RATE