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
 |