implemented more logic

This commit is contained in:
Monica Moniot
2022-10-03 11:40:03 -04:00
parent 85c819b96f
commit b01d773702
5 changed files with 297 additions and 11 deletions

17
.vscode/launch.json vendored
View File

@@ -12,6 +12,7 @@
"manageMod": true,
"adjustMods": {
"debugadapter": true,
"flib": true,
"mtc": true,
},
"disableExtraMods": true
@@ -21,13 +22,25 @@
"request": "launch",
"name": "Factorio Mod Debug (Settings & Data)",
"hookSettings": true,
"hookData": true
"hookData": true,
"adjustMods": {
"debugadapter": true,
"flib": true,
"mtc": true,
},
"disableExtraMods": true
},
{
"type": "factoriomod",
"request": "launch",
"name": "Factorio Mod Debug (Profile)",
"hookMode": "profile"
"hookMode": "profile",
"adjustMods": {
"debugadapter": true,
"flib": true,
"mtc": true,
},
"disableExtraMods": true
}
]
}

View File

@@ -1,12 +1,6 @@
{
"factorio.package.autoCommitAuthor": "Dessix <dessix@dessix.net>",
"factorio.package.autoCommitAuthor": "Mami",
"factorio.versions": [
{
"name": "Steam Factorio",
"factorioPath": "${userHome}/.local/share/Steam/steamapps/common/Factorio/bin/x64/factorio",
"docsPath": "../../../doc-html/runtime-api.json",
"configPath": "${userHome}/.factorio/config/config.ini"
},
{
"name": "Steam",
"factorioPath": "C:\\Program Files (x86)\\Steam\\steamapps\\common\\Factorio\\bin\\x64\\factorio.exe",

BIN
mtc/graphics/place.png Normal file
View File

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.5 KiB

View File

@@ -1,8 +1,11 @@
{
"name": "mtc",
"version": "0.0.1",
"title": "Many to Many Train Controller",
"title": "mtc",
"author": "Mami",
"factorio_version": "1.1",
"dependencies": ["base"]
"dependencies": [
"base",
"flib >= 0.6.0"
]
}

276
mtc/scripts/controller.lua Normal file
View File

@@ -0,0 +1,276 @@
local math = math
local INF = math.huge
local function icpairs(a, start_i)
if #a == 0 then
return nil
end
start_i = start_i%#a + 1
local i = start_i - 1
local flag = true
return function()
i = i%#a + 1
if i ~= start_i or flag then
flag = false
local v = a[i]
if v then
return i, v
end
end
end
end
--[[
station: {
deliveries_total: int
train_limit: int
priority: int
last_delivery_tick: int
r_threshold: int >= 0
p_threshold: int >= 0
train_layout: [ [ {
[car_type]: true|nil
} ] ]
accepted_layouts: {
[layout_id]: true|nil
}
}
train: {
layout_id: int
depot_id: int
}
available_trains: [{
layout_id: int
capacity: int
}]
]]
local function get_signals(stations, station_id)
return {}
end
local function get_station_dist(stations, id0, id1)
return INF
end
local function get_valid_train(stations, r_station_id, p_station_id, available_trains)
--NOTE: this code is the critical section for run-time optimization
local r_station = stations[r_station_id]
local p_station = stations[p_station_id]
local p_to_r_dist = get_station_dist(stations, p_station_id, r_station_id)
if p_to_r_dist == INF then
return nil, p_to_r_dist
end
local best_train = nil
local best_dist = INF
for k, train in pairs(available_trains.all) do
--check cargo capabilities
--check layout validity for both stations
--TODO: add check for correct cargo type
if r_station.accepted_layouts[train.layout_id] and p_station.accepted_layouts[train.layout_id] then
--check if exists valid path
--check if path is shortest so we prioritize locality
local d_to_p_dist = get_station_dist(stations, train.depot_id, p_station_id)
local dist = d_to_p_dist + p_to_r_dist
if dist < best_dist then
best_dist = dist
best_train = train
end
end
end
return best_train, best_dist
end
local function send_train_between(stations, r_station_id, p_station_id, train, ticks_total)
local r_station = stations[r_station_id]
local p_station = stations[p_station_id]
local requests = {}
local orders = {}
local has_liquid = false
local has_solid = false
local r_signals = get_signals(r_station_id)
for k, v in pairs(r_signals) do
local item_name = v.signal.name
local item_count = v.count
local item_type = v.signal.type
if item_name and item_type and item_type ~= "virtual" then
local effective_item_count = item_count + r_station.delivery_amount[item_name]
if -effective_item_count >= r_station.r_threshold then
requests[item_name] = -effective_item_count
end
end
end
local p_signals = get_signals(r_station_id)
for k, v in pairs(p_signals) do
local item_name = v.signal.name
local item_count = v.count
local item_type = v.signal.type
if item_name and item_type and item_type ~= "virtual" then
local effective_item_count = item_count + p_station.delivery_amount[item_name]
if effective_item_count >= p_station.p_threshold then
local r = requests[item_name]
if r then
orders[item_name] = math.min(r, effective_item_count)
if item_type == "liquid" then--TODO: here add liquid detection
has_liquid = true
else
has_solid = true
end
end
end
end
end
r_station.last_delivery_tick = ticks_total
p_station.last_delivery_tick = ticks_total
r_station.deliveries_total = r_station.deliveries_total + 1
p_station.deliveries_total = p_station.deliveries_total + 1
for item_name, item_count in pairs(orders) do
assert(item_count > 0, "main.lua error, transfer amount was not positive")
r_station.delivery_amount[item_name] = r_station.delivery_amount[item_name] + item_count
p_station.delivery_amount[item_name] = p_station.delivery_amount[item_name] - item_count
--set train orders
end
end
function tick(stations, available_trains, ticks_total)
local r_stations_all = {}
local p_stations_all = {}
local all_items = {}
for station_id, station in pairs(stations) do
if station.deliveries_total < station.train_limit then
station.r_threshold = 0
station.p_threshold = 0
station.priority = 0
local signals = get_signals(station_id)
for k, v in pairs(signals) do
local item_name = v.signal.name
local item_count = v.count
local item_type = v.signal.type
if item_name and item_type then
if item_type == "virtual" then
if item_name == SIGNAL_PRIORITY then
station.priority = item_count
elseif item_name == REQUEST_THRESHOLD then
station.r_threshold = math.abs(item_count)
elseif item_name == PROVIDE_THRESHOLD then
station.p_threshold = math.abs(item_count)
end
signals[k] = nil
end
else
signals[k] = nil
end
end
for k, v in pairs(signals) do
local item_name = v.signal.name
local item_count = v.count
local effective_item_count = item_count + station.delivery_amount[item_name]
if -effective_item_count >= station.r_threshold then
if r_stations_all[item_name] == nil then
r_stations_all[item_name] = {}
p_stations_all[item_name] = {}
all_items[#all_items + 1] = item_name
end
table.insert(r_stations_all[item_name], station_id)
elseif effective_item_count >= station.p_threshold then
if r_stations_all[item_name] == nil then
r_stations_all[item_name] = {}
p_stations_all[item_name] = {}
all_items[#all_items + 1] = item_name
end
table.insert(p_stations_all[item_name], station_id)
end
end
end
end
local failed_because_missing_trains_total = 0
--psuedo-randomize what item (and what station) to check first so if trains available is low they choose orders psuedo-randomly
for _, item_name in icpairs(all_items, ticks_total) do
--we do not dispatch more than one train per station per tick
local r_stations = r_stations_all[item_name]
local p_stations = p_stations_all[item_name]
--NOTE: this is an approximation algorithm for solving the assignment problem (bipartite graph weighted matching), the true solution would be to implement the simplex algorithm (and run it twice to compare the locality solution to the round-robin solution) but I strongly believe most factorio players would prefer run-time efficiency over perfect train routing logic
if #r_stations > 0 and #p_stations > 0 then
if #r_stations <= #p_stations then
--probably backpressure, prioritize locality
for i, r_station_id in icpairs(r_stations, ticks_total) do
local best = 0
local best_train = nil
local best_dist = INF
local highest_prior = -INF
local could_have_been_serviced = false
for j, p_station_id in ipairs(p_stations) do
local train, d = get_valid_train(stations, r_station_id, p_station_id, available_trains)
local prior = stations[p_station_id].priority
if prior > highest_prior or (prior == highest_prior and d < best_dist) then
if train then
best = j
best_dist = d
best_train = train
highest_prior = prior
elseif d < INF then
could_have_been_serviced = true
end
end
end
if best > 0 then
send_train_between(stations, r_station_id, p_stations[best], best_train)
table.remove(p_stations, best)
elseif could_have_been_serviced then
failed_because_missing_trains_total = failed_because_missing_trains_total + 1
end
end
else
--prioritize round robin
for j, p_station_id in icpairs(p_stations, ticks_total) do
local best = 0
local best_train = nil
local lowest_tick = INF
local highest_prior = -INF
local could_have_been_serviced = false
for i, r_station_id in ipairs(r_stations) do
local r_station = stations[r_station_id]
local prior = r_station.priority
if prior > highest_prior or (prior == highest_prior and r_station.last_delivery_tick < lowest_tick) then
local train, d = get_valid_train(stations, r_station_id, p_station_id, available_trains)
if train then
best = i
best_train = train
lowest_tick = r_station.last_delivery_tick
highest_prior = prior
elseif d < INF then
could_have_been_serviced = true
end
end
end
if best > 0 then
send_train_between(stations, r_stations[best], p_station_id, best_train)
table.remove(r_stations, best)
elseif could_have_been_serviced then
failed_because_missing_trains_total = failed_because_missing_trains_total + 1
end
end
end
end
end
end