diff --git a/better-air-filtering/control.lua b/better-air-filtering/control.lua index 852f6e6..6d92ad3 100644 --- a/better-air-filtering/control.lua +++ b/better-air-filtering/control.lua @@ -17,7 +17,7 @@ local function sign(x) return x / math.abs(x) end -function positionToChunk(position) +local function positionToChunk(position) return { x = math.floor(position.x / 32), y = math.floor(position.y / 32) } end @@ -29,7 +29,7 @@ function movePollution(surface, chunkFrom, chunkTo, amount) end function getBasePurificationRate(entity) - -- Depends mostly on recipe. Should be multiplied by crafting speed to achieve actual max purification rate + -- Depends mostly on recipe (optimal recipe used per machine). Should be multiplied by crafting speed to achieve actual max purification rate if entity.name == "air-filter-machine-1" then return 4 * INTERVAL / 60 -- 4 max pollution cleaning per second among mk1 recipes elseif entity.name == "air-filter-machine-2" or entity.name == "air-filter-machine-3" then @@ -39,6 +39,15 @@ function getBasePurificationRate(entity) end end +function energyCraftingModifier(entity) + -- Approximation to speed modifier for machine running out of power + if entity.electric_buffer_size then + return entity.energy / entity.electric_buffer_size + else + return 1 + end +end + function getSuctionRate(entity) if not entity.is_crafting() and getSpaceForPollution(entity) == 0 then return 0 @@ -51,14 +60,6 @@ function getAbsorptionRate(entity) return math.min(getSpaceForPollution(entity), getSuctionRate(entity)) end -function energyCraftingModifier(entity) - -- Approximation to speed modifier for machine running out of power - if entity.electric_buffer_size then - return entity.energy / entity.electric_buffer_size - else - return 1 - end -end function getSpaceForPollution(entity) if #entity.fluidbox < 1 then @@ -73,7 +74,7 @@ function getSpaceForPollution(entity) return capacity - pollution end -function inRadius(filter, radius) +local function inRadius(filter, radius) if filter.name == "air-filter-machine-1" then return radius <= 0 elseif filter.name == "air-filter-machine-2" then @@ -85,12 +86,17 @@ function inRadius(filter, radius) end end +local function manhattan(x, y) + -- Manhattan distance from origin to xy. + return math.abs(x) + math.abs(y) +end + -- ##################### -- # Update script # -- ##################### function absorbPollution(event) - game.print("insertPollution") +-- game.print("insertPollution") for _, c in pairs(global.air_filtered_chunks) do absorbChunk(c) end @@ -101,40 +107,52 @@ function absorbChunk(chunk) return end - local totalAbsorption = 0.0 - for _, filter in pairs(chunk.filters) do - local absorptionRate = getAbsorptionRate(filter) - totalAbsorption = totalAbsorption + absorptionRate - end + local totalAbsorptionRate = chunk:getTotalAbsorptionRate() - if totalAbsorption == 0 then +-- game.print("totalAbsorptionRate: " .. totalAbsorptionRate) + game.print("filter count: " .. #chunk.filters) + + if totalAbsorptionRate == 0 then return end - local toAbsorb = math.min(chunk:get_pollution() , totalAbsorption) - -- game.print("To absorb: " .. toAbsorb) + local toAbsorb = math.min(chunk:get_pollution() , totalAbsorptionRate) + game.print("To absorb: " .. toAbsorb) local totalInsertedAmount = 0.0 for _, filter in pairs(chunk.filters) do - local toInsert = (getAbsorptionRate(filter) / totalAbsorption) * toAbsorb + local toInsert = (getAbsorptionRate(filter) / totalAbsorptionRate) * toAbsorb if toInsert > 0 then local insertedAmount = filter.insert_fluid({ name = "pollution", amount = toInsert }) totalInsertedAmount = totalInsertedAmount + insertedAmount end end - -- game.print("Total inserted: " .. totalInsertedAmount) + game.print("Total inserted: " .. totalInsertedAmount) assert(math.abs(toAbsorb - totalInsertedAmount) < 0.01, "Error with inserting pollution in air filter machine. Different amounts absorbed/inserted: " .. toAbsorb .. " absorbed and " .. totalInsertedAmount .. " inserted.") chunk:pollute(-totalInsertedAmount) end +local function suctionUpdateChunk(chunkTo, dx, dy) +-- local totalSuction = chunkTo:getTotalSuctionRate(manhattan(dx, dy)) + +-- if totalSuction == 0 then +-- return +-- end + +-- game.print("suction: " .. totalSuction) + + +-- local chunkFrom = getFilteredChunk(chunkTo.surface, chunkTo.x + dx, chunkTo.y + dy) + +end local function generateSuctionFunction(dx, dy) local function suctionUpdate(event) - game.print("suck pollution " .. dx .. ", " .. dy) --- for _, chunkTo in pairs(global.air_filtered_chunks) do --- local chunkFrom = getFilteredChunk(chunkTo.surface, chunkTo.x + dx, chunkTo.y + dy) --- end +-- game.print("suck pollution " .. dx .. ", " .. dy) + for _, chunkTo in pairs(global.air_filtered_chunks) do + suctionUpdateChunk(chunkTo, dx, dy) + end end return suctionUpdate @@ -173,6 +191,9 @@ end local function generateFunctions() local functions = {} + -- Debug statement +-- table.insert(functions, init) + table.insert(functions, absorbPollution) for radius = 1, 4 do @@ -218,80 +239,130 @@ local function spreadOverTicks(functions, interval) end -local function FilteredChunk(surface, x, y) - local self = { - surface = surface, - x = x, - y = y, - filters = {}, - } +-- ##################### +-- # FilteredChunk # +-- ##################### - local function equal(self, other) - return self.surface.name == other.surface.name and self.x == other.x and self.y == other.y - end +--local function FilteredChunk(surface, x, y) +-- local self = { +-- surface = surface, +-- x = x, +-- y = y, +-- filters = {}, +-- } +-- +---- self.getTotalSuctionRate = getTotalSuctionRate +---- self.getTotalAbsorptionRate = getTotalAbsorptionRate +---- self.get_pollution = get_pollution +---- self.pollute = pollute +---- self.toPosition = toPosition +---- self.addFilter = addFilter +---- self.removeFilter = removeFilter +-- return self +--end - local function addToMap() - game.print("Adding chunk to map") - local chunkListX = global.air_filtered_chunks_map[surface.name] or {} - local chunkListY = chunkListX[x] or {} - assert(chunkListY[y] == nil, "Chunklist entry should not exist yet.") - chunkListY[y] = self - chunkListX[x] = chunkListY - global.air_filtered_chunks_map[surface.name] = chunkListX - table.insert(global.air_filtered_chunks, self) - end +local FilteredChunk = { + surface = nil, + x = 0, + y = 0, + filters = {}, +} - local function removeFromMap() - game.print("Removing chunk from map") - table.remove(global.air_filtered_chunks_map[surface.name][x], y) - for i, c in pairs(global.air_filtered_chunks) do - if equal(self, c) then - table.remove(global.air_filtered_chunks, i) - break - end - end - end - - local function get_pollution(self) - return self.surface.get_pollution(self:toPosition()) - end - - local function pollute(self, amount) - self.surface.pollute(self:toPosition(), amount) - end - - local function toPosition(self) - return { x = self.x * 32, y = self.y * 32 } - end - - local function addFilter(self, filter) - table.insert(self.filters, filter) - if #self.filters == 1 then - addToMap() - end - end - - local function removeFilter(self, filter) - for i, f in pairs(self.filters) do - if f.unit_number == filter.unit_number then - table.remove(self.filters, i) - break - end - end - if #self.filters == 0 then - removeFromMap() - end - end - - self.get_pollution = get_pollution - self.pollute = pollute - self.toPosition = toPosition - self.addFilter = addFilter - self.removeFilter = removeFilter - return self +function FilteredChunk:equal(other) + return self.surface.name == other.surface.name and self.x == other.x and self.y == other.y end -local function getFilteredChunk(surface, x, y) +function FilteredChunk:addToMap() + game.print("Adding chunk to map") + local chunkListX = global.air_filtered_chunks_map[self.surface.name] or {} + local chunkListY = chunkListX[self.x] or {} + assert(chunkListY[y] == nil, "Chunklist entry should not exist yet.") + chunkListY[self.y] = self + chunkListX[self.x] = chunkListY + global.air_filtered_chunks_map[self.surface.name] = chunkListX + table.insert(global.air_filtered_chunks, self) +end + +function FilteredChunk:removeFromMap() + game.print("Removing chunk from map") + table.remove(global.air_filtered_chunks_map[self.surface.name][self.x], self.y) + for i, c in pairs(global.air_filtered_chunks) do + if self:equal(c) then + table.remove(global.air_filtered_chunks, i) + game.print("Removing chunk from list") + break + end + end +end + +function FilteredChunk:getTotalAbsorptionRate() + local totalAbsorptionRate = 0.0 + for _, filter in pairs(self.filters) do + local absorptionRate = getAbsorptionRate(filter) + totalAbsorptionRate = totalAbsorptionRate + absorptionRate + end + return totalAbsorptionRate +end + +function FilteredChunk:getTotalSuctionRate(distance) + local totalSuctionRate = 0.0 + for _, filter in pairs(self.filters) do + if inRadius(filter, distance) then + local suctionRate = getSuctionRate(filter) + totalSuctionRate = totalSuctionRate + suctionRate + end + end + return totalSuctionRate +end + +function FilteredChunk:get_pollution() + return self.surface.get_pollution(self:toPosition()) +end + +function FilteredChunk:pollute(amount) + self.surface.pollute(self:toPosition(), amount) +end + +function FilteredChunk:toPosition() + return { x = self.x * 32, y = self.y * 32 } +end + +function FilteredChunk:addFilter(filter) + table.insert(self.filters, filter) + if #self.filters == 1 then + self:addToMap() + end +end + +function FilteredChunk:removeFilter(filter) + for i, f in pairs(self.filters) do + if f.unit_number == filter.unit_number then + table.remove(self.filters, i) + break + end + end + if #self.filters == 0 then + self:removeFromMap() + end +end + +function FilteredChunk:new (o) + o = o or {} + setmetatable(o, self) + self.__index = self + return o +end + +function createFilteredChunk(surface, x, y) + local chunk = FilteredChunk:new(nil) + chunk.surface = surface + chunk.x = x + chunk.y = y + chunk.filters = {} -- this statement, though it appears to have no effect, has a large impact on the saving of global state. + return chunk +end + +function getFilteredChunk(surface, x, y) local chunkListX = global.air_filtered_chunks_map[surface.name] if chunkListX ~= nil then local chunkListY = chunkListX[x] @@ -302,7 +373,7 @@ local function getFilteredChunk(surface, x, y) end end end - return FilteredChunk(surface, x, y) + return createFilteredChunk(surface, x, y) end @@ -316,14 +387,10 @@ function isAirFilterMachine(entity) end function OnEntityCreated(event) - game.print("entity created") - game.print(event.created_entity.name) if isAirFilterMachine(event.created_entity) then - game.print("air filter created") local chunkPos = positionToChunk(event.created_entity.position) local chunk = getFilteredChunk(event.created_entity.surface, chunkPos.x, chunkPos.y) chunk:addFilter(event.created_entity) - game.print("Air filter added to chunk") end end @@ -339,6 +406,12 @@ script.on_event({ defines.events.on_built_entity, defines.events.on_robot_built_ script.on_event({ defines.events.on_pre_player_mined_item, defines.events.on_robot_pre_mined, defines.events.on_entity_died }, OnEntityRemoved) +function refreshMetatables() + for i, chunk in pairs(global.air_filtered_chunks) do + FilteredChunk:new(chunk) -- reset metatable + end +end + function init() game.print("Init") -- gather all filters on every surface @@ -362,6 +435,8 @@ script.on_init(init) script.on_configuration_changed(init) script.on_load(function() + refreshMetatables() + local functions = generateFunctions() local onTick = spreadOverTicks(functions, INTERVAL)