z_mirror/PipeVisualizer
git clone https://git.raiguard.me/z_mirror/PipeVisualizer
9443616
main
1local flib_math =require ( "__flib__.math" ) 2local flib_queue =require ( "__flib__.queue" ) 3 4local entity_data =require ( "scripts.entity-data" ) 5local renderer =require ( "scripts.renderer" ) 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 in_queue table<UnitNumber, boolean> 15--- @field next_color_index integer 16--- @field player_index PlayerIndex 17--- @field queue Queue<ToIterateData> 18--- @field systems table<FluidSystemID, FluidSystemData?> 19--- @field to_ignore table<UnitNumber, boolean> 20 21--- @class FluidSystemData 22--- @field color Color 23--- @field from_hover boolean 24--- @field order uint 25 26--- @class ToIterateData 27--- @field entity LuaEntity 28--- @field unit_number UnitNumber 29 30--- @param iterator Iterator 31--- @param entity LuaEntity 32local function push ( iterator , entity ) 33if not entity . valid then 34return 35end 36local unit_number =entity . unit_number --[[@as UnitNumber]] 37iterator . in_queue [ unit_number ] =true 38flib_queue . push_back ( iterator . queue , { entity =entity , unit_number =unit_number } ) 39end 40 41--- @param iterator Iterator 42--- @return LuaEntity? 43local function pop ( iterator ) 44while true do 45local to_iterate =flib_queue . pop_front ( iterator . queue ) 46if not to_iterate then 47return 48end 49local unit_number =to_iterate . unit_number 50iterator . in_queue [ unit_number ] =nil 51if iterator . to_ignore [ unit_number ] then 52iterator . to_ignore [ unit_number ] =nil 53elseif to_iterate . entity . valid then 54return to_iterate . entity 55end 56end 57end 58 59--- @param entity LuaEntity 60--- @param player_index PlayerIndex 61--- @param in_overlay boolean 62--- @param from_hover boolean? 63--- @return boolean accepted 64local function request ( entity , player_index , in_overlay , from_hover ) 65if not global . iterator then 66return false 67end 68 69if global . blacklist [ entity . name ] then 70return false 71end 72 73local self =global . iterator [ player_index ] 74if not self then 75--- @type Iterator 76self ={ 77entities ={} , 78in_overlay =in_overlay , 79in_queue ={} , 80next_color_index =0 , 81player_index =player_index , 82queue =flib_queue . new (), 83scheduled ={} , 84systems ={} , 85to_ignore ={} , 86} 87end 88 89local unit_number =entity . unit_number 90if not unit_number then 91return false 92end 93if self . to_ignore [ unit_number ] then 94self . to_ignore [ unit_number ] =nil 95end 96if self . entities [ unit_number ] or self . in_queue [ unit_number ] then 97return self . in_overlay -- To handle entities that cross chunk boundaries 98end 99 100from_hover =from_hover or false 101 102local fluidbox =entity . fluidbox 103local should_iterate =false 104for fluidbox_index =1 , # fluidbox do 105local fluid_system_id =fluidbox . get_fluid_system_id ( fluidbox_index ) 106if not fluid_system_id then 107goto continue 108end 109local system =self . systems [ fluid_system_id ] 110if system and not in_overlay then 111goto continue 112end 113 114if not system then 115local color ={ r =0.4 , g =0.4 , b =0.4 } 116local order =flib_math . max_uint 117if global . color_by_system [ self . player_index ] then 118self . next_color_index =self . next_color_index + 1 119local next_color =global . system_colors [ self . next_color_index ] 120if next_color then 121color =next_color 122order =self . next_color_index 123end 124else 125local contents =fluidbox . get_fluid_system_contents ( fluidbox_index ) 126if contents and next ( contents ) then 127color =global . fluid_colors [ next ( contents )] 128order =global . fluid_order [ next ( contents )] 129end 130end 131 132self . systems [ fluid_system_id ] ={ color =color , from_hover =from_hover , order =order } 133end 134 135should_iterate =true 136 137:: continue :: 138end 139 140if should_iterate then 141push ( self , entity ) 142global . iterator [ player_index ] =self 143end 144 145return should_iterate 146end 147 148--- @param iterator Iterator 149--- @param entities_per_tick integer 150local function iterate ( iterator , entities_per_tick ) 151for _ =1 , entities_per_tick do 152local entity =pop ( iterator ) 153if not entity then 154break 155end 156 157-- If the entity data already existed, this entity was requested to be redrawn 158local data =entity_data . create ( iterator , entity ) 159if not data then 160return 161end 162 163renderer . draw ( iterator , data ) 164 165if iterator . in_overlay or iterator . in_queue [ entity . unit_number ] then 166goto continue 167end 168 169-- Propagate to undrawn neighbours 170for fluid_system_id , connections in pairs ( data . connections ) do 171if iterator . systems [ fluid_system_id ] then 172for _ , connection in pairs ( connections ) do 173local owner =connection . target_owner 174if owner and not global . blacklist [ owner . name ] then 175local data =entity_data . get ( iterator , owner ) 176if not data or data . connections [ fluid_system_id ] and not data . connection_objects [ fluid_system_id ] then 177local unit_number =owner . unit_number --[[@as UnitNumber]] 178if not iterator . in_queue [ unit_number ] then 179push ( iterator , owner ) 180end 181end 182end 183end 184end 185end 186 187:: continue :: 188end 189end 190 191--- @param iterator Iterator 192--- @param fluid_system_id FluidSystemID 193local function clear_system ( iterator , fluid_system_id ) 194iterator . systems [ fluid_system_id ] =nil 195for _ , data in pairs ( iterator . entities ) do 196if data . connections [ fluid_system_id ] then 197entity_data . remove_system ( iterator , data , fluid_system_id ) 198end 199end 200if not next ( iterator . systems ) then 201global . iterator [ iterator . player_index ] =nil 202end 203end 204 205--- @param player_index PlayerIndex 206--- @param entity LuaEntity 207local function clear ( player_index , entity ) 208if not global . iterator or not entity . valid then 209return 210end 211local self =global . iterator [ player_index ] 212if not self then 213return 214end 215local unit_number =entity . unit_number 216if not unit_number then 217return 218end 219if self . in_queue [ unit_number ] then 220self . to_ignore [ unit_number ] =true 221end 222local data =entity_data . get ( self , entity ) 223if not data then 224return 225end 226entity_data . remove ( self , data ) 227end 228 229--- @param player_index PlayerIndex 230local function clear_all ( player_index ) 231if not global . iterator then 232return 233end 234local it =global . iterator [ player_index ] 235if not it then 236return 237end 238for _ , entity_data in pairs ( it . entities ) do 239renderer . clear ( entity_data ) 240end 241global . iterator [ player_index ] =nil 242if not next ( global . iterator ) then 243renderer . reset () 244end 245end 246 247--- @param player_index PlayerIndex 248local function clear_hovered ( player_index ) 249if not global . iterator then 250return 251end 252local it =global . iterator [ player_index ] 253if not it then 254return 255end 256for system_id , system_data in pairs ( it . systems ) do 257if system_data . from_hover then 258clear_system ( it , system_id ) 259end 260end 261if not next ( it . systems ) then 262global . iterator [ player_index ] =nil 263if not next ( global . iterator ) then 264renderer . reset () 265end 266end 267end 268 269--- @param player_index PlayerIndex 270--- @return Iterator? 271local function get ( player_index ) 272if not global . iterator then 273return 274end 275return global . iterator [ player_index ] 276end 277 278--- @param entity LuaEntity 279--- @param player_index PlayerIndex 280local function request_or_clear ( entity , player_index ) 281local it =get ( player_index ) 282if not it then 283request ( entity , player_index , false ) 284return 285end 286if it . in_overlay then 287return 288end 289-- If this entity was being hovered, change its systems to persistent instead of removing the visualization. 290local entity_data =entity_data . get ( it , entity ) 291if entity_data then 292local did_change 293for fluid_system_id in pairs ( entity_data . connections ) do 294local system_data =it . systems [ fluid_system_id ] 295if system_data and system_data . from_hover then 296system_data . from_hover =false 297did_change =true 298end 299end 300if did_change then 301return 302end 303end 304if request ( entity , player_index , false ) then 305return 306end 307local fluidbox =entity . fluidbox 308for fluidbox_index =1 , # fluidbox do 309--- @cast fluidbox_index uint 310local id =fluidbox . get_fluid_system_id ( fluidbox_index ) 311if id and it . systems [ id ] then 312clear_system ( it , id ) 313end 314end 315end 316 317local function on_tick () 318if not global . iterator then 319return 320end 321local entities_per_tick = 322math . ceil ( settings . global [ "pv-entities-per-tick" ]. value --[[@as int]] / table_size ( global . iterator )) 323for _ , iterator in pairs ( global . iterator ) do 324iterate ( iterator , entities_per_tick ) 325end 326end 327 328--- @param e EventData.CustomInputEvent 329local function on_toggle_hover ( e ) 330local iterator =global . iterator [ e . player_index ] 331if iterator and iterator . in_overlay then 332return 333end 334local player =game . get_player ( e . player_index ) 335if not player then 336return 337end 338local entity =player . selected 339if not entity then 340clear_all ( e . player_index ) 341return 342end 343request_or_clear ( entity , e . player_index ) 344end 345 346local function reset () 347--- @type table<PlayerIndex, Iterator> 348global . iterator ={} 349end 350 351--- @class iterator 352local iterator ={} 353 354iterator . on_init =reset 355iterator . on_configuration_changed =reset 356 357iterator . events ={ 358[ defines . events . on_tick ] =on_tick , 359[ "pv-visualize-selected" ] =on_toggle_hover , 360} 361 362iterator . clear =clear 363iterator . clear_all =clear_all 364iterator . clear_hovered =clear_hovered 365iterator . get =get 366iterator . request =request 367 368return iterator