z_mirror/PipeVisualizer

git clone https://git.raiguard.me/z_mirror/PipeVisualizer

Caleb HeuerStore pipe connections in global, split and clean up code9371b54

2.0
6.0 KiB229 linesraw
1local flib_queue = require("__flib__/queue")
2
3local entity_data = require("__PipeVisualizer__/scripts/entity-data")
4local renderer = require("__PipeVisualizer__/scripts/renderer")
5local util = require("__PipeVisualizer__/scripts/util")
6
7--- @alias FlowDirection "input"|"output"|"input-output"
8--- @alias FluidSystemID uint
9--- @alias PlayerIndex uint
10
11--- @class Iterator
12--- @field entities table<UnitNumber, EntityData>
13--- @field in_overlay boolean
14--- @field player_index PlayerIndex
15--- @field queue Queue<{entity: LuaEntity, update_neighbours: boolean}>
16--- @field systems table<FluidSystemID, Color>
17
18--- @param entity LuaEntity
19--- @param player_index PlayerIndex
20--- @param in_overlay boolean
21--- @return boolean accepted
22local function request(entity, player_index, in_overlay)
23  if not global.iterator then
24    return false
25  end
26
27  local iterator = global.iterator[player_index]
28  if not iterator then
29    --- @type Iterator
30    iterator = {
31      entities = {},
32      in_overlay = in_overlay,
33      player_index = player_index,
34      queue = flib_queue.new(),
35      systems = {},
36    }
37  end
38
39  local fluidbox = entity.fluidbox
40  local should_iterate = false
41  for fluidbox_index, fluid_system_id in util.iterate_fluid_systems(fluidbox) do
42    local system = iterator.systems[fluid_system_id]
43    if system and not in_overlay then
44      goto continue
45    end
46
47    if not system then
48      -- TODO: Handle when there's no fluid in the system
49      local color = { r = 0.3, g = 0.3, b = 0.3 }
50      local contents = fluidbox.get_fluid_system_contents(fluidbox_index)
51      if contents and next(contents) then
52        color = global.fluid_colors[next(contents)]
53      end
54
55      iterator.systems[fluid_system_id] = color
56    end
57
58    should_iterate = true
59
60    ::continue::
61  end
62
63  if should_iterate then
64    flib_queue.push_back(iterator.queue, { entity = entity })
65    global.iterator[player_index] = iterator
66  end
67
68  return should_iterate
69end
70
71--- @param iterator Iterator
72--- @param entity LuaEntity
73--- @param update_neighbours boolean?
74local function request2(iterator, entity, update_neighbours)
75  update_neighbours = update_neighbours or false
76  flib_queue.push_back(iterator.queue, { entity = entity, update_neighbours = update_neighbours })
77end
78
79--- @param iterator Iterator
80--- @param entities_per_tick integer
81local function iterate(iterator, entities_per_tick)
82  for _ = 1, entities_per_tick do
83    local to_iterate = flib_queue.pop_front(iterator.queue)
84    if not to_iterate then
85      break
86    end
87    local entity, update_neighbours = to_iterate.entity, to_iterate.update_neighbours
88    if not entity.valid then
89      goto continue
90    end
91
92    -- If the entity data already existed, this entity was requested to be redrawn
93    local data = entity_data.create(iterator, entity)
94    if not data then
95      return
96    end
97
98    renderer.draw(iterator, data)
99
100    if iterator.in_overlay and not update_neighbours then
101      goto continue
102    end
103
104    -- Propagate to undrawn neighbours
105    for fluid_system_id, connections in pairs(data.connections) do
106      if iterator.systems[fluid_system_id] then
107        for _, connection in pairs(connections) do
108          local owner = connection.target_owner
109          if owner then
110            local data = entity_data.get(iterator, owner)
111            if
112              update_neighbours
113              or not data
114              or (data.connections[fluid_system_id] and not data.connection_objects[fluid_system_id])
115            then
116              flib_queue.push_back(iterator.queue, { entity = owner })
117            end
118          end
119        end
120      end
121    end
122
123    ::continue::
124  end
125end
126
127--- @param iterator Iterator
128--- @param fluid_system_id FluidSystemID
129local function clear_system(iterator, fluid_system_id)
130  iterator.systems[fluid_system_id] = nil
131  for _, data in pairs(iterator.entities) do
132    if data.connections[fluid_system_id] then
133      entity_data.remove_system(iterator, data, fluid_system_id)
134    end
135  end
136  if not next(iterator.systems) then
137    global.iterator[iterator.player_index] = nil
138  end
139end
140
141--- @param player_index PlayerIndex
142local function clear_all(player_index)
143  if not global.iterator then
144    return
145  end
146  local it = global.iterator[player_index]
147  if not it then
148    return
149  end
150  for _, entity_data in pairs(it.entities) do
151    renderer.clear(entity_data)
152  end
153  global.iterator[player_index] = nil
154end
155
156--- @param entity LuaEntity
157--- @param player_index PlayerIndex
158local function request_or_clear(entity, player_index)
159  if not global.iterator then
160    return
161  end
162  local iterator = global.iterator[player_index]
163  if not iterator then
164    request(entity, player_index, false, false)
165    return
166  end
167  if iterator.in_overlay then
168    return
169  end
170  if request(entity, player_index, false, false) then
171    return
172  end
173  -- TODO: Rewrite this
174  local fluidbox = entity.fluidbox
175  for fluidbox_index = 1, #fluidbox do
176    --- @cast fluidbox_index uint
177    local id = fluidbox.get_fluid_system_id(fluidbox_index)
178    if id and iterator.systems[id] then
179      clear_system(iterator, id)
180    end
181  end
182end
183
184local function on_tick()
185  if not global.iterator then
186    return
187  end
188  local entities_per_tick = math.ceil(30 / table_size(global.iterator))
189  for _, iterator in pairs(global.iterator) do
190    iterate(iterator, entities_per_tick)
191  end
192end
193
194--- @param e EventData.CustomInputEvent
195local function on_toggle_hover(e)
196  local iterator = global.iterator[e.player_index]
197  if iterator and iterator.in_overlay then
198    return
199  end
200  local player = game.get_player(e.player_index)
201  if not player then
202    return
203  end
204  local entity = player.selected
205  if not entity then
206    clear_all(e.player_index)
207    return
208  end
209  request_or_clear(entity, e.player_index)
210end
211
212--- @class iterator
213local iterator = {}
214
215function iterator.on_init()
216  --- @type table<PlayerIndex, Iterator>
217  global.iterator = {}
218end
219
220iterator.events = {
221  [defines.events.on_tick] = on_tick,
222  ["pv-visualize-selected"] = on_toggle_hover,
223}
224
225iterator.clear_all = clear_all
226iterator.request = request
227iterator.request2 = request2
228
229return iterator