196 lines
5.9 KiB
Lua
196 lines
5.9 KiB
Lua
local lib = require('lib')
|
|
local floor = math.floor
|
|
|
|
local cycles = 6
|
|
|
|
local states = {
|
|
collecting = 1,
|
|
carrying = 2,
|
|
waiting_for_space = 3,
|
|
depositing = 4,
|
|
returning = 5,
|
|
waiting_for_items = 6,
|
|
}
|
|
|
|
local beltlike = {
|
|
['linked-belt'] = true,
|
|
['loader-1x1'] = true,
|
|
['loader'] = true,
|
|
['splitter'] = true,
|
|
['transport-belt'] = true,
|
|
['underground-belt'] = true,
|
|
}
|
|
|
|
local state_machine = {
|
|
[states.collecting] = function(watch)
|
|
local inserter = watch.inserter
|
|
local cur_pos = inserter.held_stack_position
|
|
local pick_pos = inserter.pickup_position
|
|
local pick_entity = inserter.pickup_target
|
|
if pick_entity and beltlike[pick_entity.type] then
|
|
if floor(cur_pos.x) ~= floor(pick_pos.x) or floor(cur_pos.y) ~= floor(pick_pos.y) then
|
|
return states.carrying
|
|
end
|
|
else -- must be container-like and therefore inserter won't move around while collecting
|
|
if cur_pos.x ~= pick_pos.x or cur_pos.y ~= pick_pos.y then
|
|
return states.carrying
|
|
end
|
|
end
|
|
return nil
|
|
end,
|
|
[states.carrying] = function(watch)
|
|
local inserter = watch.inserter
|
|
local cur_pos = inserter.held_stack_position
|
|
local drop_pos = inserter.drop_position
|
|
if cur_pos.x == drop_pos.x and cur_pos.y == drop_pos.y then
|
|
return states.waiting_for_space
|
|
end
|
|
local held_stack = inserter.held_stack
|
|
local item_count = held_stack.valid_for_read and held_stack.count or 0
|
|
if item_count < watch.item_count then
|
|
return states.waiting_for_space
|
|
end
|
|
return nil
|
|
end,
|
|
[states.waiting_for_space] = function(watch)
|
|
local held_stack = watch.inserter.held_stack
|
|
local item_count = held_stack.valid_for_read and held_stack.count or 0
|
|
if item_count < watch.item_count then
|
|
return states.depositing
|
|
end
|
|
return nil
|
|
end,
|
|
[states.depositing] = function(watch)
|
|
if not watch.inserter.held_stack.valid_for_read then
|
|
return states.returning
|
|
end
|
|
return nil
|
|
end,
|
|
[states.returning] = function(watch)
|
|
local inserter = watch.inserter
|
|
local cur_pos = inserter.held_stack_position
|
|
local pick_pos = inserter.pickup_position
|
|
if cur_pos.x == pick_pos.x and cur_pos.y == pick_pos.y then
|
|
return states.waiting_for_items
|
|
end
|
|
if inserter.held_stack.valid_for_read and watch.item_count == 0 then
|
|
return states.waiting_for_items
|
|
end
|
|
return nil
|
|
end,
|
|
[states.waiting_for_items] = function(watch)
|
|
if watch.inserter.held_stack.valid_for_read and watch.item_count == 0 then
|
|
return states.collecting
|
|
end
|
|
return nil
|
|
end,
|
|
}
|
|
|
|
local function tick(watch, tick)
|
|
local new_state = watch.state
|
|
local ended = false
|
|
local changed = false
|
|
repeat
|
|
new_state = state_machine[new_state](watch)
|
|
if not new_state then
|
|
break
|
|
end
|
|
changed = true
|
|
watch.state = new_state
|
|
if new_state == states.collecting then
|
|
local cycle = watch.cycle + 1
|
|
watch.cycle = cycle
|
|
if cycle > cycles then
|
|
ended = true
|
|
end
|
|
elseif new_state == states.waiting_for_space then
|
|
watch.counts[watch.cycle] = watch.item_count
|
|
end
|
|
watch.timestamps[new_state][watch.cycle] = tick
|
|
until ended
|
|
local held_stack = watch.inserter.held_stack
|
|
watch.item_count = held_stack.valid_for_read and held_stack.count or 0
|
|
watch.hand_position = watch.inserter.held_stack_position
|
|
return ended
|
|
end
|
|
|
|
local function new(inserter)
|
|
local timestamps = {}
|
|
for _, state in pairs(states) do
|
|
timestamps[state] = {}
|
|
end
|
|
local held_stack = inserter.held_stack
|
|
return {
|
|
inserter = inserter,
|
|
cycle = 0,
|
|
-- a hack to make the cycle start unambiguously when inserter picks up items
|
|
state = states.waiting_for_items,
|
|
item_count = held_stack.valid_for_read and held_stack.count or 0,
|
|
hand_position = inserter.held_stack_position,
|
|
timestamps = timestamps,
|
|
counts = {},
|
|
}
|
|
end
|
|
|
|
local function get_statistics(series)
|
|
local min, max, sum = series[1], series[1], series[1]
|
|
for i = 2, cycles do
|
|
if series[i] < min then
|
|
min = series[i]
|
|
elseif series[i] > max then
|
|
max = series[i]
|
|
end
|
|
sum = sum + series[i]
|
|
end
|
|
return min, max, sum
|
|
end
|
|
|
|
local waiting_state = {
|
|
[states.waiting_for_space] = true,
|
|
[states.waiting_for_items] = true,
|
|
}
|
|
|
|
local function result(watch)
|
|
if watch.cycle <= cycles then
|
|
error('Unable to get the result of an ongoing stopwatch.')
|
|
end
|
|
local stable = true
|
|
local timestamps = watch.timestamps
|
|
local total_time = 0
|
|
for state, state_timestamps in pairs(timestamps) do
|
|
local diffs = {}
|
|
if timestamps[state + 1] then
|
|
for i = 1, cycles do
|
|
diffs[i] = timestamps[state + 1][i] - state_timestamps[i]
|
|
end
|
|
else
|
|
for i = 1, cycles do
|
|
diffs[i] = timestamps[states.collecting][i + 1] - state_timestamps[i]
|
|
end
|
|
end
|
|
local min, max, sum = get_statistics(diffs)
|
|
if min ~= max or waiting_state[state] and sum > 0 then
|
|
stable = false
|
|
end
|
|
total_time = total_time + sum
|
|
end
|
|
local hand_size = lib.get_stack_size(watch.inserter, watch.inserter.prototype)
|
|
local item_min, item_max, item_sum = get_statistics(watch.counts)
|
|
if item_min ~= item_max or item_sum ~= hand_size * cycles then
|
|
stable = false
|
|
end
|
|
-- 60 ticks per second
|
|
return {
|
|
throughput = (item_sum * 60 / total_time),
|
|
stable = stable,
|
|
items = item_sum,
|
|
ticks = total_time,
|
|
}
|
|
end
|
|
|
|
return {
|
|
new = new,
|
|
tick = tick,
|
|
get_throughput = result,
|
|
}
|