local lib = starlit.mod.lib
local recentJobs do
local T,G = lib.marshal.t, lib.marshal.g
recentJobs = G.array(8, T.str)
end
local function getRecentJobs(state)
for i,v in ipairs(state.pgm.file.body.conf) do
if v.key == 'recent' then
return recentJobs.dec(v.value), i
end
end
return {}
end
local function buyPrintJob(state, invs, cost, scm)
-- consume consumables
-- FIXME handle cost.leftovers from partially-consumed items
for _, v in pairs(cost.consume) do
-- assert(v.inv == 1, 'impossible condition (wrong inventory ID)')
local i = invs[v.inv]
i.hnd:set_stack(i.list, v.slot, v.remain)
end
local output = ItemStack(scm.sw.output):get_definition()
local fab = scm.sw.input
local job_t = starlit.store.compilerJob
local conf = state.pgm.file.body.conf
table.insert(conf, {
key='job', value=job_t.enc {
schematic = scm.id;
cyclesLeft = scm.sw.cost and scm.sw.cost.cycles or 0;
timeLeft = (fab.time and fab.time.print or 0);
powerLeft = cost.power;
numinaLeft = cost.numina;
}
})
end
local function compilerCanPrint(user, cpl, scm)
local E = starlit.mod.electronics
local output = ItemStack(scm.sw.output):get_definition()
local fab = scm.sw.input
local sw = scm.sw
local ok, consume, unsat, leftover, itemSpec = fab:seek {
user.entity:get_inventory():get_list 'main';
}
local cost = {
fab = fab, output = output;
consume = consume, unsat = unsat, leftover = leftover, itemSpec = itemSpec;
runtimeEstimate = scm.speed + cpl.speed + (fab.time and fab.time.print or 0);
power = cpl.powerCost + scm.powerCost
+ (sw.input.cost and sw.input.cost.power);
ram = (cpl.cost and cpl.cost.ram or 0)
+ (scm.cost and scm.cost.ram or 0);
cycles = (cpl.cost and cpl.cost.cycles or 0)
+ (scm.cost and scm.cost.cycles or 0);
numina = fab.cost and fab.cost.numina or 0;
}
local userComp = E.chip.sumCompute(
user.entity:get_inventory():get_list 'starlit_suit_chips'
)
if ok and cost.power <= user:suitCharge() and cost.ram <= userComp.ram then
return true, cost
else return false, cost end
end
starlit.alg.compilerCanPrint = compilerCanPrint
-- TODO destroy suit interfaces when power runs out or suit/chip is otherwise disabled
starlit.interface.install(starlit.type.ui {
id = 'starlit:compile-matter-component';
sub = {
suit = function(state, user, evt)
if evt.kind == 'disrobe' then state:close()
elseif evt.kind == 'power' and evt.mode == 'off' then state:close() end
end;
playerInventory = function(state,user)
-- refresh
end;
};
pages = {
index = {
setupState = function(state, user, ctx)
local E = starlit.mod.electronics
state.pgm = ctx.program
state.ctx = ctx
state.select = {}
if ctx.context == 'suit' then
state.fetch = function()
local cst = user.entity:get_inventory():get_list 'starlit_suit_chips'
local cl = {order={}, map={}, slot={}}
for i, c in ipairs(cst) do
if not c:is_empty() then
local d = E.chip.read(c)
local co = {
stack = c;
data = d;
}
table.insert(cl.order, co)
cl.map[d.uuid] = co
cl.slot[i] = co
end
end
-- kill me fam
if ( state.select.chip
and state.select.chip ~= true
and not cl.map[state.select.chip])
or (state.select.scm
and not state.select.scms[state.select.scm])
then
-- chip or pgm no longer available
user:suitSound 'starlit-error'
state.select = {}
end
state.select.chips = cl
state.select.scms = {}
if state.select.chip then
state.select.scms = E.chip.usableSoftware(cst,nil, function(s)
if state.select.chip ~= true then
if cl.slot[s.chipSlot].data.uuid ~= state.select.chip then
return false
end
end
return s.sw.kind == 'schematic'
end)
end
end
end
end;
onClose = function(state, user)
user:suitSound 'starlit-quit'
end;
refresh = true;
handle = function(state, user, q)
local E = starlit.mod.electronics
if not state.ctx.verify() then state.close() end
local inv = user.entity:get_inventory()
local sel = state.select
state.fetch()
local chips = state.select.chips
local function chirp()
user:suitSound 'starlit-nav'
end
-- are we signalling a reprint?
for k in next, q do
local idx = k:match'^recent_(%d+)$'
if idx then
local jobs = getRecentJobs(state)
local scmid = jobs[tonumber(idx)]
if not scmid then return false end
-- get list of available schematics and make sure this is on it
local cst = inv:get_list 'starlit_suit_chips'
local all = E.chip.usableSoftware(cst, nil, function(s)
return s.sw.kind == 'schematic'
end)
local scm
for i, s in ipairs(all) do
if s.id == scmid then scm = s goto found end
end
::fail:: do
user:suitSound 'starlit-error'
return false
end
::found::
local ok,cost = compilerCanPrint(user, state.pgm, scm)
if not ok then goto fail end
user:suitSound 'starlit-configure'
buyPrintJob(state, {{hnd=inv,list='main'}}, cost, scm)
state.ctx.saveConf()
return true
end
end
local function trySelection(id)
if sel[id] == nil then
for k in next, q do
local pat = "^"..id.."_(%d+)$" -- ew
local idx = k:match(pat)
if idx then
local cm = tonumber(idx)
if cm then
chirp()
sel[id] = cm
return true
end
end
end
end
end
if sel.chip == nil then
if q.showAll then
chirp()
sel.chip = true
return true
elseif q.find then
chirp()
-- TODO
return true
end
end
if trySelection('chip') then
return true
elseif trySelection('scm') then
return true
else
if q.back then
chirp()
if sel.input then
sel.input = nil
elseif sel.scm then
sel.scm = nil
elseif sel.chip then
sel.chip = nil
end
return true
elseif q.commit then
if not sel.input then
chirp()
sel.input = true
return true
else
local scm = sel.scms[sel.scm]
local ok, cost = compilerCanPrint(user, state.pgm, scm)
if ok then
user:suitSound 'starlit-configure'
buyPrintJob(state, {{hnd=inv,list='main'}}, cost, scm)
-- add recent print
local rctList, pairIdx = getRecentJobs(state)
if #rctList >= 30 then
table.remove(rctList)
end
if not lib.tbl.has(rctList, scm.id) then
table.insert(rctList,1,scm.id)
end
local conf = state.pgm.file.body.conf
local encoded = recentJobs.enc(rctList)
if pairIdx then
conf[pairIdx].value = encoded
else
table.insert(conf, {key='recent', value=encoded})
end
-- write chip
state.ctx.saveConf()
-- clear selection
state.select = {}
return true
else
user:suitSound 'starlit-error'
end
end
end
end
end;
render = function(state, user)
local sel, pgmSelector = state.select, {kind = 'vert'}
state.fetch()
state.ctx.pullConf()
local function pushSelector(tbl, id, item, label, desc, req, w)
w = w or 12
local rh = .5
local label = {kind = 'text', w = w-1.5, h=1.6;
text = '<global valign=middle>'..lib.str.htsan(label) }
if req then
label.h = label.h - rh - .2
local imgs = {}
for ci,c in ipairs(req) do
for ei, e in ipairs(c.list) do
table.insert(imgs, {kind = 'img', w=rh, h=rh, img=e.img})
end
end
label = {kind = 'vert', w = w-1.5, h=1.8;
label;
{kind ='hztl', w=w-1.5, h=rh; unpack(imgs); }
}
end
table.insert(tbl, {kind = 'hztl', w=w,h=1.8;
{kind = 'contact', id=id, w=1.5, h=1.5;
item = item;
color = {hue=220, sat=0, lum=0};
desc = desc;
};
label;
})
end
local back = {kind = 'button', id='back', label = '<- Back', w=12,h=1.2}
if sel.chips == nil then
table.insert(pgmSelector, {kind = 'img', img = 'starlit-ui-alert.png', w=2, h=2})
elseif sel.chip == nil then
for i, c in ipairs(sel.chips.order) do
-- TODO filter out chips without schematics?
pushSelector(pgmSelector, 'chip_' .. c.data.uuid, c.stack, c.data.label)
end
if next(sel.chips.order) then
table.insert(pgmSelector, {kind = 'hztl', w=12,h=1.5;
{kind = 'button', w=6,h=1.5; id='showAll', label='Show All'};
{kind = 'button', w=6,h=1.5; id='find', label='Find'};
})
end
else
if sel.scm == nil then
-- l m a o
-- pgmSelector.kind = 'hwrap'
-- pgmSelector.cols = 2
local wrap = {kind='hwrap',cols=2,w=12}
for idx, ent in ipairs(sel.scms) do
local fab = ent.sw.input
if fab.flag and fab.flag.print then
local req = fab:visualize()
pushSelector(wrap,'scm_' .. idx, ent.sw.output, ent.sw.name, nil, req,6)
end
end
table.insert(pgmSelector, wrap)
table.insert(pgmSelector, back)
else
local scm = sel.scms[sel.scm]
local output = ItemStack(scm.sw.output):get_definition()
local fab = scm.sw.input
local sw = scm.sw
local function unmet(str)
return lib.color(1,.3,.3):fmt(str)
end
table.insert(pgmSelector, {kind = 'hztl', w=12, h=1.2;
{kind = 'img', item = sw.output, w=1.2, h=1.2, desc=output.description};
{kind = 'text', text = string.format('<global valign=middle><b>%s</b>', lib.str.htsan(sw.name)), w=12-1.2,h=1.2};
})
local inputTbl = {kind = 'vert', w=6,h=0;
{kind = 'hbar', w=6, h=.5, text=sel.input and 'Input Plan' or 'Input'}};
local costTbl = {kind = 'vert', w=6,h=0;
{kind = 'hbar', w=6, h=.5, text=sel.input and 'Process Plan' or 'Process'}};
local reqPane = {kind = 'pane', id='reqPane', w=12, h=8;
{kind = 'hztl', w=12,h=0; inputTbl, costTbl}
}
local function pushCost(x, t, val)
table.insert(costTbl, {kind='label', w=5.5,h=.5,x=x;
text=string.format('%s: %s',t,val);
})
end
local function pushComputeCosts(header, p)
if p then
table.insert(costTbl, {kind = 'label', w=6, h=.5, x=0; text=header});
if p.cycles then
pushCost(.5, 'Compute', lib.math.siUI({'cycle','cycles'}, p.cycles, true))
end
if p.power then
local str = lib.math.siUI('J', p.power)
if p.power > user:suitCharge() then str = unmet(str) end
pushCost(.5, 'Power', str)
end
if p.ram then
local str = string.format("%s / %s",
lib.math.siUI('B', p.ram),
lib.math.siUI('B', state.pgm.comp.ram))
if p.ram > state.pgm.comp.ram then str = unmet(str) end
pushCost(.5, 'Memory', str)
end
end
end
local function fabToUI(x, inputTbl, req)
for ci,c in ipairs(req) do
table.insert(inputTbl, {kind = 'label', w=6-x, h=.5, x=x;
text=lib.str.capitalize(c.header)});
for ei,e in ipairs(c.list) do
table.insert(inputTbl, {kind = 'hztl', w=5.5-x, h=.5, x=x+.5;
{kind='img', w=.5,h=.5, img=e.img};
{kind='label', w=4.3,h=.5,x=.2, text=lib.str.capitalize(e.label)};
});
end
end
end
local commitHue, commitLabel = 120
if not sel.input then
commitLabel = 'Plan'
fabToUI(0, inputTbl, fab:visualize())
local function pushComputeCostsSw(header, p)
if p.sw.cost then
pushComputeCosts(header, {
cycles = p.sw.cost.cycles;
power = p.powerCost;
ram = p.sw.cost.ram;
})
end
end
pushComputeCostsSw('Schematic', scm)
pushComputeCostsSw('Compiler', state.pgm)
else
commitLabel = 'Commit'
pushComputeCosts('Total', {
cycles = (scm.sw.cost and scm.sw.cost.cycles or 0)
+ (state.pgm.sw.cost and state.pgm.sw.cost.cycles or 0);
power = (scm.powerCost or 0)
+ (state.pgm.powerCost or 0)
+ (fab.cost and fab.cost.power or 0);
ram = (scm.sw.cost and scm.sw.cost.ram or 0)
+ (state.pgm.sw.cost and state.pgm.sw.cost.ram or 0);
})
if fab.time and fab.time.print then
pushCost(0, 'Job Runtime', lib.math.timespec(fab.time.print + scm.speed))
pushCost(.5, 'Print Time', lib.math.timespec(fab.time.print))
pushCost(.5, 'CPU Time', lib.math.timespec(scm.speed + state.pgm.speed))
end
local ok, compileCost = compilerCanPrint(user, state.pgm, scm)
fabToUI(0, inputTbl, compileCost.itemSpec:visualize())
if next(compileCost.unsat) then
local vis = compileCost.unsat:visualize()
for si, s in ipairs(vis) do
s.header = 'Missing ' .. s.header
for ei, e in ipairs(s.list) do
e.label = lib.color(1,.2,.2):fmt(e.label)
end
end
fabToUI(0, inputTbl, vis)
end
if not ok then commitHue = 0 end
end
table.insert(pgmSelector, reqPane)
table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.2;
{kind = 'button', id='back', label = '← Back', w=6,h=1.2};
{kind = 'button', id='commit', label = commitLabel .. ' →', w=6,h=1.2, color={hue=commitHue,sat=0,lum=0}};
})
end
end
local rec = getRecentJobs(state)
local recentList = {kind = 'hwrap', cols=3}
if rec then
for i,id in ipairs(rec) do
local out = ItemStack(starlit.item.sw.db[id].output)
table.insert(recentList, {kind='contact',w=1.5,h=1.5,id=string.format('recent_%s',i), item = out})
end
end
local queue = {kind='pane', w=5, h=9.5}
local cst = state.ctx.availableChips()
for i,v in ipairs(state.pgm.file.body.conf) do
if v.key == 'job' then
local job = starlit.store.compilerJob.dec(v.value)
local scm = starlit.mod.electronics.chip.usableSoftware(cst, {{
id = job.schematic;
sw = starlit.item.sw.db[job.schematic];
}})[1]
if scm == nil then goto badJob end
local steps = {
{label = 'Compute', n = job.cyclesLeft, color=lib.color(0,1,0);
total = scm.sw.cost and scm.sw.cost.cycles or 0};
{label = 'Energize', n = job.powerLeft, color=lib.color(0,.5,1);
total = (scm.powerCost or 0)
+ (state.pgm.powerCost or 0)
+ (scm.sw.input.cost and scm.sw.input.cost.power or 0)};
{label = 'Assemble', n = job.timeLeft, color=lib.color(1,.2,0);
total = scm.sw.input.time and scm.sw.input.time.print or 0};
{label = 'Numenize', n = job.numinaLeft, color=lib.color(1,0,1);
total = scm.sw.cost and scm.sw.cost.numina or 0};
}
local bar = {kind = 'hbar', text = 'Complete', fac=1, w=5-1.5, h = .5}
for i, s in ipairs(steps) do
if s.n > 0 then
local pct = 1 - s.n/s.total
bar.color = s.color
bar.fac = pct
bar.text = string.format("%s %s%%", s.label, math.floor(pct*100))
break
end
end
local out = ItemStack(scm.sw.output)
table.insert(queue, {kind='hztl'; w=6, h=1.5;
{kind = 'contact', w=1.5,h=1.5, id=string.format('cancel_%s', i), item = out};
{kind = 'vert', w=6-1.5, h=1.5;
{kind = 'label', text=out:get_definition().short_description, w=6-1.5, h=.75};
bar;
}
})
end
::badJob::
end
return starlit.ui.build {
kind = 'hztl', padding = 0.5; w = 22, h = 11, mode = 'sw';
{kind = 'vert', w = 5, h = 15;
{kind = 'hbar', fac=0, w = 5, h = .5, text = '<b><left>Recent Prints</left></b>'};
recentList;
};
{kind = 'vert', w = 12, h = 11;
{kind = 'hbar', fac=0, w = 12, h = .5, text = '<b>Program Select</b>'};
{kind = 'pane', w = 12, h = 10.5, id='pgmSelect';
pgmSelector
};
};
{kind = 'vert', w = 5, h = 11;
{kind = 'hbar', fac=0, w = 5, h = .5, text = '<b><right>Print Queue</right></b>'};
queue;
};
}
end;
};
};
})