factoriolib / flib

A set of high-quality, commonly-used utilities for creating Factorio mods.
https://mods.factorio.com/mod/flib
MIT License
61 stars 15 forks source link

Async processing library #32

Closed Zomis closed 3 years ago

Zomis commented 3 years ago

I have started working on my own asynchronous processing library, and using it in two of my mods Lamp Placer and What is Missing. I think it would be very beneficial if an async processing library would be included in FLib.

Links to my library and usages:

The main ideas of the library are as follows:

As code cannot be stored in the global object, functions to dynamically get the items to iterate given the previous loop values cannot be stored over time but needs to be configured by the mod. For example, given a surface and an entity-type, the function finds the entities matching. (This is a use-case scenario in What is Missing)

Feel free to use the existing Async code I have written, or adapt it or draw inspiration from it in any way. I know it's not perfect and could definitely use a review.

raiguard commented 3 years ago

Have you looked at table.for_n_of? It lets you easily spread a task over multiple ticks without a bunch of overhead.

Zomis commented 3 years ago

@raiguard Yeah I am aware that it exists and I could possibly make some use of it, but that alone does not cover all the things I would want. For example something like "In 2000 ticks, do X" does not need table.for_n_of

raiguard commented 3 years ago

You can very easily do that by making a tasks table in global and sticking things in it keyed by the tick they should happen on. I don't think we need a lib to do that. I did seriously consider adding an on_tick_n to the event module, but decided against it for that reason.

And as for your second point about nested loop structures, you can do that by passing a custom next() function to for_n_of. Here is a working example of that.

raiguard commented 3 years ago
local event = require("__flib__.event")

local function add_task(tick, payload)
  local tasks = global.tasks[tick]
  if not tasks then
    tasks = {}
    global.tasks[tick] = tasks
  end
  tasks[#tasks + 1] = payload
end

event.on_init(function()
  global.tasks = {}
end)

event.on_player_created(function(e)
  add_task(game.tick + 10, {action = "greet!", player_index = e.player_index})
end)

event.on_tick(function(e)
  local tasks = global.tasks[e.tick]
  if tasks then
    for _, task in ipairs(tasks) do
      if task.action == "greet!" then
        local player = game.get_player(task.player_index)
        player.print("Hello, "..player.name.."!")
      elseif task.action == "something_else" then
        -- do something else
      end
    end
    global.tasks[e.tick] = nil
  end
end)

Here's a very simple delayed execution thing. I didn't test it, but it should work? Hopefully.

raiguard commented 2 years ago

So, I actually decided to add a delayed execution module. I'm sorry for being stubborn before - it really is quite useful! The module is named on-tick-n. It isn't documented yet, but it's fully functional. Give it a try if you like!

raiguard commented 2 years ago

on-tick-n was added in v0.8.0. You can view the documentation here. It's pretty simple, but pretty effective!