Moon Logic deprecated

by mk-fg

Adds Lua-programmable circuit network combinator. Based on Sandboxed LuaCombinator and LuaCombinator2 mods. Probably won't work in multiplayer games.

Content
2 years ago
1.0 - 1.1
4.97K
Circuit network

g [lua-qiz] How to wait for signal?

3 years ago
(updated 3 years ago)

I try this

while red['signal-green'] ~= 1 do end

and game halt :)

ps Can I interrupt such programm without kill the game?

3 years ago

ps Can I interrupt such programm without kill the game?

Hm, no, don't think you can, unless factorio has some setting to "limit running lua code to X seconds" or something and halts it externally.
Don't think modding api allows to set such time limit anywhere.

From the mod perspective, lua code on the combinator is written in turing-complete language (lua), so there's no reliable way to tell if it'll halt or keep running forever (aka Halting Problem).

I try this
while red['signal-green'] ~= 1 do end

Combinator code runs all within same game tick, so inputs don't change during that, and neither does anything in the game world.

You can work around this by having code run and check that condition on every tick (which it does by default if you don't set "delay=").
If you want to have many lua combinators doing something like that, might be more game-performance-friendly to use decider combinator latch and check output of that on a longer intervals, though dunno how many simple checks you'd need to start noticing slowdowns on a modern hardware.

One good way to support something like "wait for something here" could've been via lua coroutines, but factorio does not support these.
Another way would be to have some signal to interrupt sleep ("delay=") on connected combinators, which you can e.g. send from decider combinator that checks the condition you want, but that's not implemented, maybe should be.

3 years ago

Thank you, I create state machine and all works fine

3 years ago
(updated 3 years ago)

while red['signal-green'] ~= 1 do end

Another way would be to have some signal to interrupt sleep ("delay=") on connected combinators, which you can e.g. send from decider combinator that checks the condition you want, but that's not implemented, maybe should be.

Added that in 0.0.56, so you can do something like this:

irq, delay = 'signal-green', 999999
...do-something-only-when-signal-green-is-set...

Which should make it easy to get MLCs to react instantly to anything you want by placing efficient decider combinators to wake up and run lua code only when you want it to.

Thank you for the idea - never crossed my mind somehow, even though it's kinda simple and very useful.

3 years ago

Hello,
is it also possible to set an interrupt on an array of multiple signals. I have a use case where I want to check for 2 signals.

3 years ago

Would be easy, and I considered adding that option, but was thinking along these lines:

  • You'd almost certainly emit interrupt signal from decider combinator, as "non-zero" is too simple condition check for most purposes.
  • Decider combinators' default mode is to check some condition and output some-signal=1, which works perfectly for interrupt-signal=1.
  • If you need to check >1 input conditions, just add >1 decider combinators, all checking their own thing and sending interrupt-signal=1 on the same wire.

Not sure if I can easily imagine the case where this won't be trivial (so why complicate things), can you maybe describe one?

3 years ago

Also, decider combinators checking for when to send interrupt in this case should be really nice visual indicators of which condition was tripped, blinking their LEDs and all!

3 years ago
(updated 3 years ago)

You are right about the decider combinator. Personally I just want to make things as compact as possible.
I have a further related suggestion. It might be nice to have an option to have a minimum irq delay as a separate variable (again this could likely be solved with additional combinators). If you could be bothered to add this.
Also in environment variables irq setting is not visible. I am using it dynamically so for me it would be a nice debug feature to see it there.
Edit:
The last one actually already works, Its just my setup which I am currently debugging that doesn't work.

3 years ago
(updated 3 years ago)

have a minimum irq delay as a separate variable

Don't think I understand what you mean here.
As in a separate delay= that triggers after irq signal is received and before code gets run?

If that's what you mean, it might be somewhat complicated to even spell out in the description (which is usually a good indicator that it's kinda nuanced or niche).
For example - is it delay from first appearance of irq signal or the most recent?
Will same irq signal appearing during that "interrupt delay" reset the delay?
Will change in irq signal during that delay reset it?
Should removing that irq signal during the delay cancel running the code?
Should it be a "debouncing" delay and code should run immediately when irq signal is detected, but next run delayed by that interval?
If latter is the case, how'd that interval interact with regular delay=?
What if code changes irq signal, should irq delay be reset?
... and so so many other questions fractally spawning from there.

Good news though, is that if you go for minimalism and only make auto-resetting "irq=" value for a single signal, everything above can be trivially implemented in your own lua code on the combinator, so that there's no need to write a 100-page manual on interrupts in that combinator :)
(it's like a combinatorics issue - the more complexity you add on the mod side, the more non-obvious combinations of interactions hidden there you'd have to describe to people, and get confused bug reports about when it's non-obvious how stuff works)

Actually, I have a bit of code that I tend to copy-paste that implements answers to most of the questions above I think, it's in JavaScript though:

let debounce = (delay_ms, mode, func) => {
  // Usage: s.on('input', debounce(300, 'last', (d, n, ns) => console.log(ns[n].value)))
  // Modes:
  //  last - delay after last call; use-case: not called on fast typing until delay passes
  //  now - now + delay; use-case: rate-limiting calls, where first one works, next one delayed
  //  first - delay after first call; use-case: same as "now" but with first call delayed as well
  let timeout, timeout_calls
  return (...args) => {
    if (mode === 'now' && !timeout) func(...args)
    if (timeout) {
      timeout_calls++
      if (mode === 'last') { window.clearTimeout(timeout); timeout = null } }
    else timeout_calls = 0
    if (!timeout) timeout = window.setTimeout(() => {
      timeout = null; if (!(mode === 'now' && !timeout_calls)) func(...args) }, delay_ms) } }

I think it might demonstrate nuances of what "delay for interaction" might mean, with a strict how-it-would-work logic there, in a more compact way than english above, given small familiarity with JS or general code.
It can easily be re-implemented in lua on the combinator (by using delay=, internal state variable, and setting/resetting irq= value depending on that internal state), and be more compact for a specific case from variants highlighted by above questions or implemented in JS above ofc.

3 years ago

Also in environment variables irq setting is not visible. I am using it dynamically so for me it would be a nice debug feature to see it there.
Edit: The last one actually already works, Its just my setup which I am currently debugging that doesn't work.

Yeah, I think it should be visible, being left in global lua environment and only reset when code runs (unless you set it there again).

3 years ago

Hello again,

what I meant by delay was in fact a debouncing delay and your code works for that.
In a combinator it can be implemented using the "var" feature. The annoying thing with it is just you need to set delay first before re nabling irq in order not not waste cycles by triggering irq and rejecting it later due to not enough time passed.

Additionally I want to update you regarding irq setting not being visible in environment variables.
The way I found it works is it is visible for only one run, but on the next on it is gone, despite it still being active. To test this I have a simple code section:

if green["signal-1"]>0 and out["signal-2"]==0 then
out["signal-2"]=1
delay=60
--irq="signal-9"

elseif green["signal-1"]==0 then
irq="signal-1"
delay=600000
out["signal-2"]=0

else
delay=60
if out["signal-2"]==4 then
out["signal-2"]=8
else
out["signal-2"]=4
end

end

This is a simpified state machine I am using with LTN distribution system. Expected behavior is when signal-1 is set signal 2 stats jumping from 4 to 8 and back until signal 1 is gone. This works correctly if --irq="signal-9" is present (not commented). When you comment this line, the delay no longer works and combinator is triggering constantly. Seems to me that irq is still set to signal-1, despite this setting being applied in a previous run and not showing in environment variables any more.

3 years ago
(updated 3 years ago)

The way I found it works is it is visible for only one run, but on the next on it is gone, despite it still being active. To test this I have a simple code section:

But that's because it is indeed gone, as it's supposed to be, no?

3 years ago

But that's because it is indeed gone, as it's supposed to be, no?

Right, looking at the code and description, I think I've missed the "despite it still being active" part.
And indeed, pretty sure I forgot to reset actual "mlc.irq" value in the code after using it, causing this bug to happen, thanks for testing and finding it!

3 years ago

Should be fixed in 0.0.57, indeed forgot to cleanup signal value that is actually used with on_tick in the same place where it gets removed from env globals.

3 years ago
(updated 3 years ago)

what I meant by delay was in fact a debouncing delay and your code works for that.
In a combinator it can be implemented using the "var" feature.
The annoying thing with it is just you need to set delay first before re nabling irq in order not not waste cycles by triggering irq and rejecting it later due to not enough time passed.

Yeah, that is maybe a good and intuitive way to describe it, and sounds like a common enough thing.

3 years ago

Thank you for the fix. Have to say this has to be the quickest response and fixing of a bug I have ever seen, very impressive.

3 years ago
(updated 3 years ago)

fixing of a bug I have ever seen, very impressive.

How long can it take to change one line :)
I think whole implementation of the "irq" feature is like 5 lines - one where it gets reset before code runs, +1 when it's checked afterwards and converted to signal-id for mlc.irq, and if mlc.irq and mlc.e.get_merged_signal(mlc.irq) ~= 0 then mlc.next_tick = nil end.

Sometimes I wonder if maybe this small mod is a pure evil trick for destroying factorio modding community, as now people make and run way more complicated mods on top of this "lua combinator" trick, instead of saving same lua code into a proper control.lua mod file and sharing them on this mod portal with everyone.

3 years ago

what I meant by delay was in fact a debouncing delay and your code works for that.

Not really relevant to the topic, but pretty sure "rate limiting" would be more apt way to describe such "subsequent triggering is delayed" behavior, while "debouncing" word should probably be used same as in electronics for "check in N ms after first triggering when signal state settles".
I just got these words confused above, and it'd be a bad idea to perpetuate such confusion :)

3 years ago

Actually I find your mod quite valuable just because I cant be bothered to learn the Factorio modding API. Also the ability to try things in game for easier debugging is valuable in itself. I am not even sure if there is some reference manual available for the modding API or perhaps some other documentation. The vanilla combinators are quite limiting because you cant do each signal times each signal or any other operation involving 2 nested for loops.

The problem I had with the mod was poor performance. I was running several for loops over the entire logistic network signal group once per second which tanked performance for 1-2 UPS. Now with interrupts I hope to change this on run only when required which could be a big improvement in performance.

Regarding the debouncing. Personally I have a background in electronic engineering so I am much more familiar with the term deboucning as opposed to rate limiting.

3 years ago

I am not even sure if there is some reference manual available for the modding API or perhaps some other documentation.

It's very well-documented with reference at https://lua-api.factorio.com/latest/ + articles and tutorials on the official factorio wiki and forum section where probably every question was already answered (which google indexes too), so very easy to get into, I think.

was running several for loops over the entire logistic network signal group once per second which tanked performance for 1-2 UPS
The vanilla combinators are quite limiting because you cant do each signal times each signal

While it can be simplier to implement stuff with nested loops, if you can get by using two loops over signals with state tables, it might be much faster and even more transparent and easy-to-use.
Layer of indirection within sandbox might make access to global variables slower, and tables like red/green have their own extra layers, so maybe such accesses can be optimized greatly by using more locals (and local tables) in the code on the combinators.
Debug overlays from Shift-F4 can also provide some numbers when doing any kind of optimization, but it might be going too deep for such an in-game task.

I have a background in electronic engineering so I am much more familiar with the term deboucning as opposed to rate limiting.

Hm, yeah, maybe it'd be invoking the right general intuition about purpose regardless of mismatch wrt details.
Feel like for compressing one-liner mod descriptions (which people would be more likely to read in-game), a background in poetry would be most useful :)

3 years ago
(updated 3 years ago)

Added hopefully-descriptive "irq_min_interval" value in 0.0.58, which is "min ticks between triggering code runs on any irq signal".
Use-case description of "To avoid complicated logic when that signal is not a pulse" and name should probably make it clear that it's a rate-limiter and not a delay of some kind.

Only with this parameter "irq" was easy to use in that complicated "you can do all things here!" example in the mod description, as logic for swapping that value before/after delay would be unnecessarily confusing for a simple and probably common usage when input is not a pulse.
(decider combinator for checking signal-T == 17 and triggering interrupt would probably make even more sense in that specific case, but it's probably better for an example to be more self-contained)

Thanks for the suggestion!

3 years ago
(updated 3 years ago)

I was running several for loops over the entire logistic network signal group once per second which tanked performance for 1-2 UPS.

Oh, also, remembered one specific note here, also mentioned in a thread elsewhere - if you have a ton of signals (like from some logistic network) and looking at an open GUI displaying them all, UPS are going to suffer a lot by default, as that GUI is updating (rebuilding) the tab with inputs and outputs every single tick by default.

There is an option to make it less stressful on the system, but setting any >1 tick value can introduce "invisible signals" which don't even flap in that tab between frames, if they are synced to that setting value, as should also be mentioned in that option description.
(e.g. if you set sampling rate to 1/60 ticks there, signal that comes and goes every 5, 10 or 30 ticks won't be noticeable in any way, hence the default)

3 years ago

Hello,

Thanks for the link to the modding API. I will save it in case I want to make a mod instead of using combinators. I will probably also post in LTN thread in Factorio forum once I get my system working again with interrupts. Basically what I have is as an advanced version of outpost delivery system with combined requester/provider system. The basic system (posted in LTN thread) is limited to 1 wagon trains and one inserter with stack size of 1. The reason why it is so limited is because you musn't overload trains with combined requester/provider stations (otherwise the train starts unlading an so it never leaves the station). I just improved the system to handle multiple wagon trains with multiple inserters and full stack size. I use this to deliver all the building material in a mega base instead of having one huge logistic network. I also implemented a hysteresis (for all signals) in outposts and just this is annoying to do with regular combinators. Basically you have to use similar trickery as when you want to multiply 2 wires of signals (each by each).

I will use the new "irq_min_interval" feature instead of implementing a state machine within the combinator. Should make code cleaner and hopefully also faster. Thank you for that.

Not sure what you wanted to say with your last post. I searched the ingame options menu for this option but couldn't find this.

3 years ago
(updated 3 years ago)

Not sure what you wanted to say with your last post. I searched the ingame options menu for this option but couldn't find this.

Just that if experience that UPS drop mostly when combinator window is open, it might be this issue:
"high UPS with lots of GUI updates" - https://mods.factorio.com/mod/Moon_Logic/discussion/5f55012c1ff93bb2ece201e7

Option to address this issue should be under "Startup" tab in Settings - Mod Settings here:

3 years ago

Ah, I was searching in the regular settings, not mod settings.
Indeed setting this to 5 helps massively when gui is open. I was getting 10-15 UPS drop previously, now with update set to 5, I get 1 UPS drop max.

Thank you.

3 years ago
(updated 3 years ago)

link to the modding API

You can kinda use it from the combinators too, via a "secret" _api variable (added there by popular request in a couple other threads here), e.g. for train logistics something like this might be useful: local wood_stack = _api.game.item_prototypes.wood.stack_size
But it's also trivial to break the game (or make it fail to save) through it of course, as there's no limits on the power available through it, so maybe best to avoid entirely.

I also always play on Rail Worlds with a lot of trains, but never really used LTNs, as I kinda like noodling railways manually and make highly distributed bases with isolated ad-hoc production lines and no central buses of any kind.

setting this to 5 helps

Value 5 is actually pretty clever, as using any prime number there makes sure that any kind of cyclical signal changes will be easily visible, since such prime number can only be divided by itself without remainder, so signals have much less chance to change back-and-forth in sync with the interval.

New response