312 lines
7.9 KiB
Lua
312 lines
7.9 KiB
Lua
local flib_math = require("__flib__/math")
|
|
local flib_queue = require("__flib__/queue")
|
|
|
|
local entity_data = require("__PipeVisualizer__/scripts/entity-data")
|
|
local renderer = require("__PipeVisualizer__/scripts/renderer")
|
|
|
|
--- @alias FlowDirection "input"|"output"|"input-output"
|
|
--- @alias FluidSystemID uint
|
|
--- @alias PlayerIndex uint
|
|
|
|
--- @class Iterator
|
|
--- @field entities table<UnitNumber, EntityData>
|
|
--- @field in_overlay boolean
|
|
--- @field in_queue table<UnitNumber, boolean>
|
|
--- @field next_color_index integer
|
|
--- @field player_index PlayerIndex
|
|
--- @field queue Queue<LuaEntity>
|
|
--- @field scheduled table<FluidSystemID, {entity: LuaEntity?, tick: uint}>
|
|
--- @field systems table<FluidSystemID, FluidSystemData?>
|
|
--- @field to_ignore table<UnitNumber, boolean>
|
|
|
|
--- @class FluidSystemData
|
|
--- @field color Color
|
|
--- @field order uint
|
|
|
|
--- @param iterator Iterator
|
|
--- @param entity LuaEntity
|
|
local function push(iterator, entity)
|
|
if not entity.valid then
|
|
return
|
|
end
|
|
local unit_number = entity.unit_number --[[@as UnitNumber]]
|
|
iterator.in_queue[unit_number] = true
|
|
flib_queue.push_back(iterator.queue, entity)
|
|
end
|
|
|
|
--- @param iterator Iterator
|
|
--- @return LuaEntity?
|
|
local function pop(iterator)
|
|
while true do
|
|
local entity = flib_queue.pop_front(iterator.queue)
|
|
if not entity then
|
|
return
|
|
end
|
|
local unit_number = entity.unit_number --[[@as UnitNumber]]
|
|
iterator.in_queue[unit_number] = nil
|
|
if iterator.to_ignore[unit_number] then
|
|
iterator.to_ignore[unit_number] = nil
|
|
else
|
|
return entity
|
|
end
|
|
end
|
|
end
|
|
|
|
--- @param entity LuaEntity
|
|
--- @param player_index PlayerIndex
|
|
--- @param in_overlay boolean
|
|
--- @return boolean accepted
|
|
local function request(entity, player_index, in_overlay)
|
|
if not global.iterator then
|
|
return false
|
|
end
|
|
|
|
local self = global.iterator[player_index]
|
|
if not self then
|
|
--- @type Iterator
|
|
self = {
|
|
entities = {},
|
|
in_overlay = in_overlay,
|
|
in_queue = {},
|
|
next_color_index = 0,
|
|
player_index = player_index,
|
|
queue = flib_queue.new(),
|
|
scheduled = {},
|
|
systems = {},
|
|
to_ignore = {},
|
|
}
|
|
end
|
|
|
|
local unit_number = entity.unit_number
|
|
if not unit_number then
|
|
return false
|
|
end
|
|
if self.to_ignore[unit_number] then
|
|
self.to_ignore[unit_number] = nil
|
|
end
|
|
if self.entities[unit_number] or self.in_queue[unit_number] then
|
|
return self.in_overlay -- To handle entities that cross chunk boundaries
|
|
end
|
|
|
|
local fluidbox = entity.fluidbox
|
|
local should_iterate = false
|
|
for fluidbox_index = 1, #fluidbox do
|
|
local fluid_system_id = fluidbox.get_fluid_system_id(fluidbox_index)
|
|
if not fluid_system_id then
|
|
goto continue
|
|
end
|
|
local system = self.systems[fluid_system_id]
|
|
if system and not in_overlay then
|
|
goto continue
|
|
end
|
|
|
|
if not system then
|
|
local color = { r = 0.4, g = 0.4, b = 0.4 }
|
|
local order = flib_math.max_uint
|
|
if global.color_by_system[self.player_index] then
|
|
self.next_color_index = self.next_color_index + 1
|
|
local next_color = global.system_colors[self.next_color_index]
|
|
if next_color then
|
|
color = next_color
|
|
order = self.next_color_index
|
|
end
|
|
else
|
|
local contents = fluidbox.get_fluid_system_contents(fluidbox_index)
|
|
if contents and next(contents) then
|
|
color = global.fluid_colors[next(contents)]
|
|
order = global.fluid_order[next(contents)]
|
|
end
|
|
end
|
|
|
|
self.systems[fluid_system_id] = { color = color, order = order }
|
|
end
|
|
|
|
should_iterate = true
|
|
|
|
::continue::
|
|
end
|
|
|
|
if should_iterate then
|
|
push(self, entity)
|
|
global.iterator[player_index] = self
|
|
end
|
|
|
|
return should_iterate
|
|
end
|
|
|
|
--- @param iterator Iterator
|
|
--- @param entities_per_tick integer
|
|
local function iterate(iterator, entities_per_tick)
|
|
for _ = 1, entities_per_tick do
|
|
local entity = pop(iterator)
|
|
if not entity then
|
|
break
|
|
end
|
|
|
|
-- If the entity data already existed, this entity was requested to be redrawn
|
|
local data = entity_data.create(iterator, entity)
|
|
if not data then
|
|
return
|
|
end
|
|
|
|
renderer.draw(iterator, data)
|
|
|
|
if iterator.in_overlay or iterator.in_queue[entity.unit_number] then
|
|
goto continue
|
|
end
|
|
|
|
-- Propagate to undrawn neighbours
|
|
for fluid_system_id, connections in pairs(data.connections) do
|
|
if iterator.systems[fluid_system_id] then
|
|
for _, connection in pairs(connections) do
|
|
local owner = connection.target_owner
|
|
if owner then
|
|
local data = entity_data.get(iterator, owner)
|
|
if not data or data.connections[fluid_system_id] and not data.connection_objects[fluid_system_id] then
|
|
local unit_number = owner.unit_number --[[@as UnitNumber]]
|
|
if not iterator.in_queue[unit_number] then
|
|
push(iterator, owner)
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
end
|
|
|
|
::continue::
|
|
end
|
|
end
|
|
|
|
--- @param iterator Iterator
|
|
--- @param fluid_system_id FluidSystemID
|
|
local function clear_system(iterator, fluid_system_id)
|
|
iterator.systems[fluid_system_id] = nil
|
|
for _, data in pairs(iterator.entities) do
|
|
if data.connections[fluid_system_id] then
|
|
entity_data.remove_system(iterator, data, fluid_system_id)
|
|
end
|
|
end
|
|
if not next(iterator.systems) then
|
|
global.iterator[iterator.player_index] = nil
|
|
end
|
|
end
|
|
|
|
--- @param player_index PlayerIndex
|
|
--- @param entity LuaEntity
|
|
local function clear(player_index, entity)
|
|
if not global.iterator or not entity.valid then
|
|
return
|
|
end
|
|
local self = global.iterator[player_index]
|
|
if not self then
|
|
return
|
|
end
|
|
local unit_number = entity.unit_number
|
|
if not unit_number then
|
|
return
|
|
end
|
|
if self.in_queue[unit_number] then
|
|
self.to_ignore[unit_number] = true
|
|
end
|
|
local data = entity_data.get(self, entity)
|
|
if not data then
|
|
return
|
|
end
|
|
entity_data.remove(self, data)
|
|
end
|
|
|
|
--- @param player_index PlayerIndex
|
|
local function clear_all(player_index)
|
|
if not global.iterator then
|
|
return
|
|
end
|
|
local it = global.iterator[player_index]
|
|
if not it then
|
|
return
|
|
end
|
|
for _, entity_data in pairs(it.entities) do
|
|
renderer.clear(entity_data)
|
|
end
|
|
global.iterator[player_index] = nil
|
|
if not next(global.iterator) then
|
|
renderer.reset()
|
|
end
|
|
end
|
|
|
|
--- @param entity LuaEntity
|
|
--- @param player_index PlayerIndex
|
|
local function request_or_clear(entity, player_index)
|
|
if not global.iterator then
|
|
return
|
|
end
|
|
local iterator = global.iterator[player_index]
|
|
if not iterator then
|
|
request(entity, player_index, false)
|
|
return
|
|
end
|
|
if iterator.in_overlay then
|
|
return
|
|
end
|
|
if request(entity, player_index, false) then
|
|
return
|
|
end
|
|
local fluidbox = entity.fluidbox
|
|
for fluidbox_index = 1, #fluidbox do
|
|
--- @cast fluidbox_index uint
|
|
local id = fluidbox.get_fluid_system_id(fluidbox_index)
|
|
if id and iterator.systems[id] then
|
|
clear_system(iterator, id)
|
|
end
|
|
end
|
|
end
|
|
|
|
local function on_tick()
|
|
if not global.iterator then
|
|
return
|
|
end
|
|
local entities_per_tick = math.ceil(30 / table_size(global.iterator))
|
|
for _, iterator in pairs(global.iterator) do
|
|
iterate(iterator, entities_per_tick)
|
|
end
|
|
end
|
|
|
|
--- @param e EventData.CustomInputEvent
|
|
local function on_toggle_hover(e)
|
|
local iterator = global.iterator[e.player_index]
|
|
if iterator and iterator.in_overlay then
|
|
return
|
|
end
|
|
local player = game.get_player(e.player_index)
|
|
if not player then
|
|
return
|
|
end
|
|
local entity = player.selected
|
|
if not entity then
|
|
clear_all(e.player_index)
|
|
return
|
|
end
|
|
request_or_clear(entity, e.player_index)
|
|
end
|
|
|
|
local function reset()
|
|
--- @type table<PlayerIndex, Iterator>
|
|
global.iterator = {}
|
|
end
|
|
|
|
--- @class iterator
|
|
local iterator = {}
|
|
|
|
iterator.on_init = reset
|
|
iterator.on_configuration_changed = reset
|
|
|
|
iterator.events = {
|
|
[defines.events.on_tick] = on_tick,
|
|
["pv-visualize-selected"] = on_toggle_hover,
|
|
}
|
|
|
|
iterator.clear = clear
|
|
iterator.clear_all = clear_all
|
|
iterator.request = request
|
|
|
|
return iterator
|