Using the LTN remote interface

Adds new train stops forming a highly configurable logistic network.

Moderator: Optera

Post Reply
eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Using the LTN remote interface

Post by eduran »

LTN has increased my enjoyment of factorio a lot, so let me start with: thank you for creating and maintaining it, Optera!

Recently I've started working on a mod for LTN. It is supposed to become a GUI that displays stats about the train network. A bit like LTN content reader, but with more info and as a UI instead of an entity. The basics are working, but I ran into a small problem. There are two events transmitting data and I need both sets to process them. Which lead me to this construct:

Code: Select all

local function parse_new_data()
  global.new_stops, global.new_dd = false, false  
  -- do stuff with global.raw and store output in global.output
 end
 
local function on_stops_updated(event)
  global.new_stops = true
  global.raw.data_stops = event.data
  if global.new_disp then
    parse_new_data()
  end  
end

local function on_dispatcher_updated(event)
  global.new_disp = true
  global.raw.data_disp = event.data
  if global.new_stops then
    parse_new_data()
  end  
end

script.on_event(remote.call("logistic-train-network", "get_on_stops_updated_event"), ltn_parser.on_stops_updated)
script.on_event(remote.call("logistic-train-network", "get_on_dispatcher_updated_event"), ltn_parser.on_dispatcher_updated) 
That means storing the event data in two globals instead of passing it on directly and relies on LTN on to send both events synchronized. From reading your code the latter seems to be the case. Still, it feels like I am doing it wrong. Is there a better way to get both sets of data into my parser at the same time?

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2916
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: Using the LTN remote interface

Post by Optera »

Glad to hear someone is working on an UI for LTN. I made this interface hoping someone would do exactly that. ;)

LTN always fires on_stops_updated before on_dispatcher_updated. Neither of them should ever be empty.
If you need both you can shorten your update trigger down to this:

Code: Select all

local function update_data()  
  -- do stuff with global.raw
 end
 
local function on_stops_updated(event)
  global.raw.data_stops = event.data
end

local function on_dispatcher_updated(event)
  global.raw.data_disp = event.data
  update_data()  
end

script.on_event(remote.call("logistic-train-network", "get_on_stops_updated_event"), ltn_parser.on_stops_updated)
script.on_event(remote.call("logistic-train-network", "get_on_dispatcher_updated_event"), ltn_parser.on_dispatcher_updated) 
This will call update_data only once and have both data sets in global.raw.

If you need more help feel free to ask.

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Using the LTN remote interface

Post by eduran »

Optera wrote:
Sun Jan 27, 2019 3:39 pm
LTN always fires on_stops_updated before on_dispatcher_updated. Neither of them should ever be empty.
Thank you, that is good to know.
At the moment I don't have LTN-specific questions, but that will certainly change :D

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Using the LTN remote interface

Post by eduran »

I think I found a bug in the interface. When disabling the Depot control signal on existing depot stops, the data doesn't update to reflect this. Below is a print of one entry, as received from the on_stops_updated event:

Code: Select all

[180820] = {
    entity = {
      LuaEntity{
        backer_name = "DEP mall, 8x",
        name = "logistic-train-stop",
        type = "train-stop"
      }
    },
    input = {
      LuaEntity{
        name = "logistic-train-stop-input",
        type = "lamp"
      }
    },
    output = {
      LuaEntity{
        name = "logistic-train-stop-output",
        type = "constant-combinator"
      }
    },
    lampControl = {
      LuaEntity{
        name = "logistic-train-stop-lamp-control",
        type = "constant-combinator"
      }
    },
    isDepot = true,   <---------- still true in here, but the input does not receive a signal
    network_id = -1,
    trainLimit = 0,
    activeDeliveries = {},
    errorCode = 2,    <----------------- correctly displaying error code 2
    minTraincars = 0,
    maxTraincars = 0,
    providePriority = 0,
    lockedSlots = 0,
    noWarnings = 0,
    parkedTrainFacesStop = true,
    reqestPriority = 0,
    parkedTrain = {
      LuaTrain{
        id = 648,
        carriages = {
          {
...
LTN itself behaves as expected and does not schedule any deliveries for the trains stopped at the depot.

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2916
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: Using the LTN remote interface

Post by Optera »

eduran wrote:
Mon Jan 28, 2019 9:51 pm
I think I found a bug in the interface. When disabling the Depot control signal on existing depot stops, the data doesn't update to reflect this. Below is a print of one entry, as received from the on_stops_updated event:
That's working as intended.
Error states are checked before signal processing is even started. It makes no sense to do the performance intensive signal processing for error states.

Check errorCode first, anything but code 0 contains signal data from before the error state was triggered. I'd write the stop name red and either leave all display values blank or write the error state next to the stop.

Error codes are:

Code: Select all

 {
  [-1] = "white", -- not initialized
  [1] = "red",    -- short circuit / disabled
  [2] = "pink",   -- duplicate stop name
}

AndrewIRL
Fast Inserter
Fast Inserter
Posts: 240
Joined: Fri Mar 24, 2017 2:17 pm
Contact:

Re: Using the LTN remote interface

Post by AndrewIRL »

@eduran Looking forward to downloading this mod when it is done.

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Using the LTN remote interface

Post by eduran »

Optera wrote:
Tue Jan 29, 2019 7:36 am
Error states are checked before signal processing is even started. It makes no sense to do the performance intensive signal processing for error states.
Check errorCode first, anything but code 0 contains signal data from before the error state was triggered.
That makes sense. Is the entity and parked train data up to date in that case? And don't you have to check for the is_depot signal to figure out if a duplicate name is an error or not?
Also, are there non-error cases which could cause the data to be old or incomplete? At the moment my code does not check the existence of every field in the stop data table. It just trusts they are there and uses them.

AndrewIRL wrote:
Tue Jan 29, 2019 9:01 am
@eduran Looking forward to downloading this mod when it is done.
Happy to hear that. It is still some way from being finished, tho. But I will post preview screenshots soon, to start a discussion about which information would be useful to see in a UI.

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2916
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: Using the LTN remote interface

Post by Optera »

eduran wrote:
Tue Jan 29, 2019 9:41 am
That makes sense. Is the entity and parked train data up to date in that case?
Entity aka the stop itself, input and output will never change, they can only become invalid in which case the entire stop entry is deleted.

Parked train is updated regardless of error code, including recalculating delivery size when it leaves the stop.
Trains wont be added to available trains in case the stop is a depot and the status lamp updates also don't happen.
eduran wrote:
Tue Jan 29, 2019 9:41 am
And don't you have to check for the is_depot signal to figure out if a duplicate name is an error or not?
Error codes are hierarchical with lower numbers overruling higher ones. Checking for duplicate name is wasted processing power if a stop already is in ErrorCode 1. However ErrorCode 1 stops are still checked against when looking for duplicate names. In that case the stop on ErrorCode 1 remains on 1 and the other stop will be set to ErrorCode 2.
eduran wrote:
Tue Jan 29, 2019 9:41 am
Also, are there non-error cases which could cause the data to be old or incomplete?
I haven't managed to force LTN into that state. All calculations done in that cycle would be wrong.
eduran wrote:
Tue Jan 29, 2019 9:41 am
At the moment my code does not check the existence of every field in the stop data table. It just trusts they are there and uses them.
I'm doing the same in LTN. Users will see and report the error to me long before the interface event fires.
Here are the stop fields that exist after stop creation with the default values:

Code: Select all

  global.LogisticTrainStops[entity.unit_number] = {
    entity = entity,
    input = input,
    output = output,
    lampControl = lampctrl,
    isDepot = false,
    network_id = -1,         --any network
    trainLimit = 0,
    activeDeliveries = {},   --delivery IDs to/from stop
    errorCode = -1,          --key to errorCodes table
    parkedTrain = nil,
    parkedTrainID = nil,
  }
These are created/reset every time a stop is updated before checking for errorCodes:
Edit: thanks for making me look this up, thresholds shouldn't default to nil, but to global setting.

Code: Select all

  stop.minProvided = min_provided
  stop.minRequested = min_requested
  stop.minTraincars = 0
  stop.maxTraincars = 0
  stop.trainLimit = 0
  stop.providePriority = 0
  stop.lockedSlots = 0
  stop.noWarnings = 0

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Using the LTN remote interface

Post by eduran »

There seem to be a few edge cases when dispatcher data and stop data are out-of-sync for one update. If a stop is destroyed, it can happen that the stop list no longer contains the stop, but the dispatcher still lists it as providing or requesting items. Is that working as intended? If so, I will have to add quite a few safety-checks ;)

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2916
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: Using the LTN remote interface

Post by Optera »

I might have already fixed that with the 1.9.8 LTN update.
Dispatcher now contains update interval.


Sorry I just read out of sync.
Here's the answer to your question:
It can take up to a full update cycle before stop changes are updated into the Dispatcher data structure.
on_stops_updated_event is instantly updated by create and removal events.

The only checks you need to do is if stopIDs from Provided and Requests exist in LogisticTrainStops.

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Using the LTN remote interface

Post by eduran »

After browsing your code for a while, I think what happens is this:

1. The OnTick function updates the (still-existing) stop and adds it to the stop list. It also updates dispatcher.Provided.
2. Stop is destroyed shortly afterwards, OnTick is still busy updating other stops.
3. on_entity_destroyed catches this event and updates the stop list, but not dispatcher.Provided
3. OnTick finishes and sends both datasets.

It won't impacs LTN, because before creating a delivery you check again if the stop exists. I guess I will have to do something similar.

Edit: You were a lot faster than me in figuring out what is going on :D

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2916
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: Using the LTN remote interface

Post by Optera »

eduran wrote:
Sun Feb 03, 2019 3:19 pm
Edit: You were a lot faster than me in figuring out what is going on :D
Having grown this mod for over 2 years now I sure hope I know how it operates. :lol:

Removing stops from Requests and especially Provided is a pain with the data structure LTN needs to quickly find where specific items are available.
I could add code to the removal event to update Dispatcher data but it would only catch normal events.
Factorio doesn't always fire on_entity_destroyed .... For example when a chunk or whole surface is removed it won't fire on_entity_destroyed for each entity...

yeahtoast
Inserter
Inserter
Posts: 21
Joined: Wed Feb 27, 2019 6:17 pm
Contact:

Extracting LTN Event Data for Use In Other Scripts/Functions

Post by yeahtoast »

For Automatic Train Painter, I'm trying to get trains dispatched by LTN to change color upon assignment and remain that way until they finish delivery. As the mod's current painting algorithm will unpaint empty trains automatically (which is fine post-delivery, but not pre-pickup), I was thinking to implement a simple function where the train-to-be-painted's ID will be looked up in the event.deliveries table and if there is a matching Train ID, it will not be painted.

The issue I have here is getting event.deliveries data from the OnDispatcherUpdated function out of the on_init/on_load scripts and into my on_event script. I'm not having any luck with global variables, nor calling the OnDispatcherUpdated function inside the on_event script, I assume because it's undefined. What can I do to export event data from the OnDispatcherUpdated function for use in the rest of my mod? Is this even possible?

eduran
Filter Inserter
Filter Inserter
Posts: 344
Joined: Fri May 09, 2014 2:52 pm
Contact:

Re: Extracting LTN Event Data for Use In Other Scripts/Functions

Post by eduran »

In on_init/on_load you need to register for the event:

Code: Select all

local on_dispatcher_updated_event = remote.call("logistic-train-network", "on_dispatcher_updated")
script.on_event(on_dispatcher_updated_event, dispatcher_updated_handler)
And your handler needs to do something like:

Code: Select all

local function dispatcher_updated_handler(event)
global.deliveries = event.delivieries
end
Now you can use global.deliveries as you see fit. Whenever LTN updates its data, the event handler runs and also updates the lookup table.

yeahtoast
Inserter
Inserter
Posts: 21
Joined: Wed Feb 27, 2019 6:17 pm
Contact:

Re: Extracting LTN Event Data for Use In Other Scripts/Functions

Post by yeahtoast »

Welp that was stupidly simple and works like a charm. Thanks!

User avatar
Optera
Smart Inserter
Smart Inserter
Posts: 2916
Joined: Sat Jun 11, 2016 6:41 am
Contact:

Re: Using the LTN remote interface

Post by Optera »

merged into existing interface thread

I've added usage to the manual listing the interface functions and properties.
viewtopic.php?f=214&t=51072#p397844

Post Reply

Return to “Logistic Train Network”