mirror of
https://github.com/Xevion/project-cybersyn.git
synced 2025-12-10 10:08:18 -06:00
implemented more logic
This commit is contained in:
17
.vscode/launch.json
vendored
17
.vscode/launch.json
vendored
@@ -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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
||||
8
.vscode/settings.json
vendored
8
.vscode/settings.json
vendored
@@ -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
BIN
mtc/graphics/place.png
Normal file
Binary file not shown.
|
After Width: | Height: | Size: 2.5 KiB |
@@ -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
276
mtc/scripts/controller.lua
Normal 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
|
||||
Reference in New Issue
Block a user