starlit  Diff

Differences From Artifact [e3ed80cb5c]:

To Artifact [c1a1690c3b]:


   168    168   					end
   169    169   				end
   170    170   				if not pgm then return false end -- HAX
   171    171   
   172    172   				-- kind=active programs must be assigned to a command slot
   173    173   				-- kind=direct programs must open their UI
   174    174   				-- kind=passive programs must toggle on and off
          175  +				local function suitCtx(pgm)
          176  +					local chips = user.entity:get_inventory():get_list 'starlit_suit_chips'
          177  +					local pgmctx = starlit.mod.electronics.chip.usableSoftware(chips, {pgm})[1]
          178  +					return {
          179  +						context = 'suit';
          180  +						program = pgmctx;
          181  +					}
          182  +				end
          183  +
   175    184   				if pgm.sw.powerKind == 'active' then
   176    185   					if cfg then
   177    186   						user:openUI(pgm.sw.ui, 'index', {
   178    187   							context = 'suit';
   179    188   							program = pgm;
   180    189   						})
   181    190   						return false
................................................................................
   189    198   					elseif pptrMatch(ptr, pnan.secondary) then
   190    199   						pnan.secondary = nil
   191    200   					else
   192    201   						pnan.secondary = ptr
   193    202   					end
   194    203   					user:suitSound 'starlit-configure'
   195    204   				elseif pgm.sw.powerKind == 'direct' then
   196         -					local ctx = {
   197         -						context = 'suit';
   198         -						program = pgm;
   199         -					}
          205  +					local ctx = suitCtx(pgm)
   200    206   					if pgm.sw.ui then
   201    207   						user:openUI(pgm.sw.ui, 'index', ctx)
   202    208   						return false
   203    209   					else
   204    210   						pgm.sw.run(user, ctx)
   205    211   					end
   206    212   				elseif pgm.sw.powerKind == 'passive' then
   207    213   					if cfg then
   208         -						user:openUI(pgm.sw.ui, 'index', {
   209         -							context = 'suit';
   210         -							program = pgm;
   211         -						})
          214  +						user:openUI(pgm.sw.ui, 'index', suitCtx(pgm))
   212    215   						return false
   213    216   					end
   214    217   
   215    218   					local addDisableRec = true
   216    219   					for i, e in ipairs(pgm.file.body.conf) do
   217    220   						if e.key == 'disable' and e.value == 'yes' then
   218    221   							addDisableRec = false
................................................................................
   326    329   						w=2, h=2;
   327    330   					})
   328    331   					menu.padding = 1;
   329    332   				end
   330    333   				return starlit.ui.build(menu)
   331    334   			end;
   332    335   		};
   333         -		compilerListRecipes = {
   334         -		};
   335    336   		psi = {
   336    337   			render = function(state, user)
   337    338   				return starlit.ui.build {
   338    339   					kind = 'vert', mode = 'sw';
   339    340   					padding = 0.5;
   340    341   				}
   341    342   			end;
................................................................................
   346    347   				local tb = {
   347    348   					kind = 'vert', mode = 'sw';
   348    349   					padding = 0.5, 
   349    350   					{kind = 'hztl', padding = 0.25;
   350    351   						{kind = 'label', text = 'Name', w = 2, h = barh};
   351    352   						{kind = 'label', text = user.persona.name, w = 4, h = barh}};
   352    353   				}
   353         -				local statBars = {'nutrition', 'hydration', 'fatigue', 'morale', 'irradiation', 'illness'}
   354         -				for idx, id in ipairs(statBars) do
   355         -					local s = starlit.world.stats[id]
   356         -					local amt, sv = user:effectiveStat(id)
   357         -					local min, max = starlit.world.species.statRange(user.persona.species, user.persona.speciesVariant, id)
          354  +				local statBars = {'stamina', 'numina', 'nutrition', 'hydration', 'fatigue', 'morale', 'irradiation', 'illness'}
          355  +				local function wrapElts(n, l)
          356  +					local all = {kind='vert'}
          357  +					local ct, row
          358  +					local function flush()
          359  +						if row then
          360  +							table.insert(all, row)
          361  +						end
          362  +						row = {kind='hztl', spacing = 0.2}
          363  +						ct = 0
          364  +					end
          365  +					flush()
          366  +					for i, e in ipairs(l) do
          367  +						ct = ct + 1
          368  +						table.insert(row, e)
          369  +						if ct >= n then flush() end
          370  +					end
          371  +					flush()
          372  +					return all
          373  +				end
          374  +				local bars = {}
          375  +				local function pushBar(s, amt, sv, min, max)
   358    376   					local st = string.format('%s / %s', s.desc(amt, true), s.desc(max))
   359         -					table.insert(tb, {kind = 'hztl', padding = 0.25;
          377  +					table.insert(bars, {kind = 'hztl', padding = 0.25;
   360    378   						{kind = 'label', w=2, h=barh, text = lib.str.capitalize(s.name)};
   361    379   						{kind = 'hbar',  w=4, h=barh, fac = sv, text = st, color=s.color};
   362    380   					})
   363    381   				end
          382  +				do local hp, hpf = user:effectiveStat 'health'
          383  +					local desc = {
          384  +						name='health';
          385  +						desc = function(hp) return tostring(hp) end;
          386  +						color = {hue=10,sat=1,lum=.5};
          387  +					}
          388  +					pushBar(desc, hp, hpf, starlit.world.species.statRange(user.persona.species, user.persona.speciesVariant, 'health'))
          389  +				end
          390  +				do local ep, ex = user:suitCharge(), user:suitPowerCapacity()
          391  +					local desc = {
          392  +						name = 'power';
          393  +						desc = function(j) return lib.math.siUI('J', j) end;
          394  +						color = {hue=190,sat=1,lum=.5};
          395  +					}
          396  +					pushBar(desc, ep, ep/ex, 0, ex)
          397  +				end
          398  +				for idx, id in ipairs(statBars) do
          399  +					local s = starlit.world.stats[id]
          400  +					local amt, sv = user:effectiveStat(id)
          401  +					local min, max = starlit.world.species.statRange(user.persona.species, user.persona.speciesVariant, id)
          402  +					pushBar(s, amt, sv, min, max)
          403  +				end
          404  +				table.insert(tb, wrapElts(2, bars))
   364    405   				local abilities = {
   365    406   					maneuver = {};
   366    407   					direct = {};
   367    408   					passive = {};
   368    409   				}
   369    410   				state.abilityMap = {}
   370    411   				for i, a in pairs(user:species().abilities) do
................................................................................
   458    499   					user:suitPowerStateSet(suitMode)
   459    500   					return true
   460    501   				end
   461    502   			end;
   462    503   		};
   463    504   	};
   464    505   })
          506  +
          507  +local function compilerCanPrint(user, cpl, scm)
          508  +	local output = ItemStack(scm.sw.output):get_definition()
          509  +	local fab = output._starlit.fab
          510  +	local sw = scm.sw
          511  +	local ok, consume, unsat, leftover, itemSpec = fab:seek {
          512  +		user.entity:get_inventory():get_list 'main';
          513  +	}
          514  +
          515  +	local cost = {
          516  +		consume = consume, unsat = unsat, leftover = leftover, itemSpec = itemSpec;
          517  +		runtimeEstimate = scm.speed + cpl.speed + (fab.time and fab.time.print or 0);
          518  +		power = cpl.powerCost + scm.powerCost;
          519  +		ram = (cpl.cost and cpl.cost.ram or 0)
          520  +		    + (scm.cost and scm.cost.ram or 0);
          521  +		cycles = (cpl.cost and cpl.cost.cycles or 0)
          522  +		       + (scm.cost and scm.cost.cycles or 0);
          523  +	}
          524  +
          525  +	local userComp = starlit.mod.electronics.chip.sumCompute(
          526  +		user.entity:get_inventory():get_list 'starlit_suit_chips'
          527  +	)
          528  +
          529  +	if ok and cost.power <= user:suitCharge() and cost.ram <= userComp.ram then
          530  +		return true, cost
          531  +	else return false, cost end
          532  +end
   465    533   
   466    534   -- TODO destroy suit interfaces when power runs out or suit/chip is otherwise disabled
   467    535   starlit.interface.install(starlit.type.ui {
   468    536   	id = 'starlit:compile-matter-component';
   469    537   	sub = {
   470    538   		suit = function(state, user, evt)
   471    539   			if evt.kind == 'disrobe' then state:close()
................................................................................
   480    548   			setupState = function(state, user, ctx)
   481    549   				state.pgm = ctx.program
   482    550   				state.select = {}
   483    551   				local E = starlit.mod.electronics
   484    552   				if ctx.context == 'suit' then
   485    553   					state.fetch = function()
   486    554   						local cst = user.entity:get_inventory():get_list 'starlit_suit_chips'
   487         -						local cl = {order={}, map={}}
          555  +						local cl = {order={}, map={}, slot={}}
   488    556   						for i, c in ipairs(cst) do
   489    557   							if not c:is_empty() then
   490    558   								local d = E.chip.read(c)
   491    559   								local co = {
   492    560   									stack = c;
   493    561   									data = d;
   494    562   								}
   495    563   								table.insert(cl.order, co)
   496    564   								cl.map[d.uuid] = co
          565  +								cl.slot[i] = co
   497    566   							end
   498    567   						end
   499         -						if state.select.chip and not cl.map[state.select.chip.data.uuid] then
   500         -							-- chip no longer available
          568  +
          569  +						-- kill me fam
          570  +						if (   state.select.chip
          571  +							and state.select.chip ~= true
          572  +							and not cl.map[state.select.chip])
          573  +						or (state.select.scm
          574  +						   and not state.select.scms[state.select.scm])
          575  +					   then
          576  +							-- chip or pgm no longer available
   501    577   							user:suitSound 'starlit-error'
   502    578   							state.select = {}
   503    579   						end
   504    580   						state.select.chips = cl
   505    581   
   506    582   						state.select.scms = {}
   507    583   						if state.select.chip then
   508         -							state.select.scms = E.chip.usableSoftware({state.select.chip.stack},nil,
   509         -								function(s) return s.sw.kind == 'schematic' end)
          584  +							state.select.scms = E.chip.usableSoftware(cst,nil, function(s)
          585  +								if state.select.chip ~= true then
          586  +									if cl.slot[s.chipSlot].data.uuid ~= state.select.chip then
          587  +										return false
          588  +									end
          589  +								end
          590  +								return s.sw.kind == 'schematic'
          591  +							end)
   510    592   						end
          593  +
   511    594   					end
   512    595   				end
   513    596   			end;
   514    597   
   515    598   			onClose = function(state, user)
   516    599   				user:suitSound 'starlit-quit'
   517    600   			end;
................................................................................
   518    601   			handle = function(state, user, q)
   519    602   				local sel = state.select
   520    603   				state.fetch()
   521    604   				local chips = state.select.chips
   522    605   				local function chirp()
   523    606   					user:suitSound 'starlit-nav'
   524    607   				end
   525         -				local function onPickChip(chip)
   526         -					chirp()
   527         -					sel.chip = chip
   528         -					return true
   529         -				end
   530         -				local function onPickScm(scm)
   531         -					chirp()
   532         -					sel.scm = scm
   533         -					return true
          608  +
          609  +				local function trySelection(id)
          610  +					if sel[id] == nil then
          611  +						for k in next, q do
          612  +							local pat = "^"..id.."_(%d+)$" -- ew
          613  +							local idx = k:match(pat)
          614  +							if idx then
          615  +								local cm = tonumber(idx)
          616  +								if cm then
          617  +									chirp()
          618  +									sel[id] = cm
          619  +									return true
          620  +								end
          621  +							end
          622  +						end
          623  +					end
   534    624   				end
   535    625   
   536    626   				if sel.chip == nil then
   537         -					for k in next, q do
   538         -						local id = k:match "^chip_(%d+)$"
   539         -						if id then
   540         -							local cm = chips.map[tonumber(id)]
   541         -							if cm then return onPickChip(cm) end
   542         -						end
          627  +					if q.showAll then
          628  +						chirp()
          629  +						sel.chip = true
          630  +						return true
          631  +					elseif q.find then
          632  +						chirp()
          633  +						-- TODO
          634  +						return true
   543    635   					end
   544         -				elseif sel.scm == nil then
   545         -					if q.back then chirp() sel.chip = nil return true end
   546         -					for k in next, q do
   547         -						local id = k:match "^scm_(%d+)$"
   548         -						if id then
   549         -							local cm = state.select.scms[tonumber(id)]
   550         -							if cm then return onPickScm(cm) end
          636  +				end
          637  +
          638  +				if trySelection('chip') then
          639  +					return true
          640  +				elseif trySelection('scm') then
          641  +					return true
          642  +				else
          643  +					if q.back then
          644  +						chirp()
          645  +						if sel.input then
          646  +							sel.input = nil
          647  +						elseif sel.scm then
          648  +							sel.scm = nil
          649  +						elseif sel.chip then
          650  +							sel.chip = nil
          651  +						end
          652  +						return true
          653  +					elseif q.commit then
          654  +						if not sel.input then
          655  +							chirp()
          656  +							sel.input = true
          657  +							return true
          658  +						else
          659  +							local scm = sel.scms[sel.scm]
          660  +							local ok, cost = compilerCanPrint(user, state.pgm, scm)
          661  +							if ok then
          662  +								user:suitSound 'starlit-configure'
          663  +								-- consume consumables
          664  +								-- add print job
          665  +								state.select = {}
          666  +								return true
          667  +							else
          668  +								user:suitSound 'starlit-error'
          669  +							end
   551    670   						end
   552    671   					end
   553         -				else
   554         -					if q.back then chirp() sel.scm = nil return true end
   555    672   				end
          673  +
   556    674   			end;
   557    675   
   558    676   			render = function(state, user)
   559    677   				local sel, pgmSelector = state.select, {}
   560    678   				state.fetch()
   561    679   
   562    680   				local function pushSelector(id, item, label, desc, req)
   563    681   					local rh = .5
   564    682   					local label = {kind = 'text', w = 10-1.5, h=1.5;
   565         -							text = '<global valign=middle>'..label }
          683  +							text = '<global valign=middle>'..lib.str.htsan(label) }
   566    684   					if req then
   567    685   						label.h = label.h - rh - .2
   568    686   
   569    687   						local imgs = {}
   570    688   						for ci,c in ipairs(req) do
   571    689   							for ei, e in ipairs(c.list) do
   572    690   								table.insert(imgs, {kind = 'img', w=rh, h=rh,  img=e.img})
................................................................................
   591    709   				if sel.chips == nil then
   592    710   					table.insert(pgmSelector, {kind = 'img', img = 'starlit-ui-alert.png', w=2, h=2})
   593    711   				elseif sel.chip == nil then
   594    712   					for i, c in ipairs(sel.chips.order) do
   595    713   					-- TODO filter out chips without schematics?
   596    714   						pushSelector('chip_' .. c.data.uuid, c.stack, c.data.label)
   597    715   					end
          716  +					if next(sel.chips.order) then
          717  +						table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.5;
          718  +							{kind = 'button', w=5,h=1.5; id='showAll', label='Show All'};
          719  +							{kind = 'button', w=5,h=1.5; id='find', label='Find'};
          720  +						})
          721  +					end
   598    722   				else
   599    723   					if sel.scm == nil then
   600    724   						for idx, ent in ipairs(sel.scms) do
   601    725   							local fab = ItemStack(ent.sw.output):get_definition()._starlit.fab
   602    726   							if fab.flag.print then
   603    727   								local req = fab:visualize()
   604    728   								pushSelector('scm_' .. idx, ent.sw.output, ent.sw.name, nil, req)
   605    729   							end
   606    730   						end
   607    731   						table.insert(pgmSelector, back)
   608    732   					else
   609         -						local output = ItemStack(sel.scm.sw.output):get_definition()
          733  +						local scm = sel.scms[sel.scm]
          734  +						local output = ItemStack(scm.sw.output):get_definition()
   610    735   						local fab = output._starlit.fab
   611         -						local sw = sel.scm.sw
          736  +						local sw = scm.sw
          737  +						local function unmet(str)
          738  +							return lib.color(1,.3,.3):fmt(str)
          739  +						end
   612    740   						table.insert(pgmSelector, {kind = 'hztl', w=10, h=1.2;
   613    741   							{kind = 'img', item = sw.output, w=1.2, h=1.2, desc=output.description};
   614         -							{kind = 'text', text = string.format('<global valign=middle><b>%s</b>', sw.name), w=10-1.2,h=1.2};
          742  +							{kind = 'text', text = string.format('<global valign=middle><b>%s</b>', lib.str.htsan(sw.name)), w=10-1.2,h=1.2};
   615    743   						})
   616    744   						local inputTbl = {kind = 'vert', w=5,h=0;
   617         -							{kind = 'hbar', w=5, h=.5, text='Input'}};
   618         -						local costTbl = {kind = 'vert', w=5,h=0; spacing=.25;
   619         -							{kind = 'hbar', w=5, h=.5, text='Process'}};
          745  +							{kind = 'hbar', w=5, h=.5, text=sel.input and 'Input Plan' or 'Input'}};
          746  +						local costTbl = {kind = 'vert', w=5,h=0;
          747  +							{kind = 'hbar', w=5, h=.5, text=sel.input and 'Process Plan' or 'Process'}};
   620    748   						local reqPane = {kind = 'pane', id='reqPane', w=10, h=7;
   621    749   							{kind = 'hztl', w=10,h=0; inputTbl, costTbl}
   622    750   						}
   623         -						local req = fab:visualize()
   624         -						for ci,c in ipairs(req) do
   625         -							table.insert(inputTbl, {kind = 'label', w=4.5, h=1, x=.5;
   626         -								text=lib.str.capitalize(c.header)});
   627         -							for ei,e in ipairs(c.list) do
   628         -								table.insert(inputTbl, {kind = 'hztl', w=4, h=.5, x=1;
   629         -									{kind='img',   w=.5,h=.5, img=e.img};
   630         -									{kind='label', w=3.3,h=.5,x=.2, text=e.label};
   631         -								});
          751  +						local function pushCost(x, t, val)
          752  +							table.insert(costTbl, {kind='label', w=4.5,h=.5,x=x;
          753  +								text=string.format('%s: %s',t,val);
          754  +							})
          755  +						end
          756  +						local function pushComputeCosts(header, p)
          757  +							if p then
          758  +								table.insert(costTbl, {kind = 'label', w=5, h=.5, x=0; text=header});
          759  +								if p.cycles then
          760  +									pushCost(.5, 'Compute', lib.math.siUI({'cycle','cycles'}, p.cycles, true))
          761  +								end
          762  +								if p.power then
          763  +									local str = lib.math.siUI('J', p.power)
          764  +									if p.power > user:suitCharge() then str = unmet(str) end
          765  +									pushCost(.5, 'Power', str)
          766  +								end
          767  +								if p.ram then
          768  +									local str = string.format("%s / %s",
          769  +										lib.math.siUI('B', p.ram),
          770  +										lib.math.siUI('B', state.pgm.comp.ram))
          771  +									if p.ram > state.pgm.comp.ram then str = unmet(str) end
          772  +									pushCost(.5, 'Memory', str)
          773  +								end
          774  +							end
          775  +						end
          776  +
          777  +						local function fabToUI(x, inputTbl, req)
          778  +							for ci,c in ipairs(req) do
          779  +								table.insert(inputTbl, {kind = 'label', w=5-x, h=.5, x=x;
          780  +									text=lib.str.capitalize(c.header)});
          781  +								for ei,e in ipairs(c.list) do
          782  +									table.insert(inputTbl, {kind = 'hztl', w=4.5-x, h=.5, x=x+.5;
          783  +										{kind='img',   w=.5,h=.5, img=e.img};
          784  +										{kind='label', w=3.3,h=.5,x=.2, text=lib.str.capitalize(e.label)};
          785  +									});
          786  +								end
   632    787   							end
   633    788   						end
   634         -						if sw.cost then
   635         -							local function pushCost(t, val)
   636         -								table.insert(costTbl, {kind='text', w=4.5,h=.5,x=.5;
   637         -									text=string.format('<b>%s</b>: %s',t,val);
   638         -								})
          789  +
          790  +						local commitHue=120, commitLabel
          791  +						if not sel.input then
          792  +							commitLabel = 'Plan'
          793  +							fabToUI(0, inputTbl, fab:visualize())
          794  +							local function pushComputeCostsSw(header, p)
          795  +								if p.sw.cost then
          796  +									pushComputeCosts(header, {
          797  +										cycles = p.sw.cost.cycles;
          798  +										power = p.powerCost;
          799  +										ram = p.sw.cost.ram;
          800  +									})
          801  +								end
          802  +							end
          803  +							pushComputeCostsSw('Schematic', scm)
          804  +							pushComputeCostsSw('Compiler', state.pgm)
          805  +						else
          806  +							commitLabel = 'Commit'
          807  +							pushComputeCosts('Total', {
          808  +								cycles = (scm.sw.cost and scm.sw.cost.cycles or 0)
          809  +								       + (state.pgm.sw.cost and state.pgm.sw.cost.cycles or 0);
          810  +								power = (scm.powerCost or 0)
          811  +								      + (state.pgm.powerCost or 0)
          812  +								      + (fab.cost and fab.cost.power or 0);
          813  +								ram = (scm.sw.cost and scm.sw.cost.ram or 0)
          814  +								    + (state.pgm.sw.cost and state.pgm.sw.cost.ram or 0);
          815  +							})
          816  +							if fab.time and fab.time.print then
          817  +								pushCost(0, 'Job Runtime', lib.math.timespec(fab.time.print + scm.speed))
          818  +								pushCost(.5, 'Print Time', lib.math.timespec(fab.time.print))
          819  +								pushCost(.5, 'CPU Time', lib.math.timespec(scm.speed + state.pgm.speed))
          820  +							end
          821  +							local ok, compileCost = compilerCanPrint(user, state.pgm, scm)
          822  +							fabToUI(0, inputTbl, compileCost.itemSpec:visualize())
          823  +
          824  +							if next(compileCost.unsat) then
          825  +								local vis = compileCost.unsat:visualize()
          826  +								for si, s in ipairs(vis) do
          827  +									s.header = 'Missing ' .. s.header
          828  +									for ei, e in ipairs(s.list) do
          829  +										e.label = lib.color(1,.2,.2):fmt(e.label)
          830  +									end
          831  +								end
          832  +								fabToUI(0, inputTbl, vis)
   639    833   							end
   640         -							if sw.cost.cycles then
   641         -								pushCost('Energy', lib.math.siUI('J', sel.scm.powerCost))
   642         -								pushCost('Compute', lib.math.siUI({'cycle','cycles'}, sw.cost.cycles, true))
   643         -							end
          834  +							if not ok then commitHue = 0 end
   644    835   						end
   645    836   						table.insert(pgmSelector, reqPane)
   646    837   						table.insert(pgmSelector, {kind = 'hztl', w=10,h=1.2;
   647         -							{kind = 'button', id='back', label = '<- Back', w=5,h=1.2};
   648         -							{kind = 'button', id='print', label = 'Print ->', w=5,h=1.2, color={hue=120,sat=0,lum=0}};
          838  +							{kind = 'button', id='back', label = '← Back', w=5,h=1.2};
          839  +							{kind = 'button', id='commit', label = commitLabel .. ' →', w=5,h=1.2, color={hue=commitHue,sat=0,lum=0}};
   649    840   						})
   650    841   					end
   651    842   				end
   652    843   
   653    844   				return starlit.ui.build {
   654    845   					kind = 'hztl', padding = 0.5; w = 20, h = 10, mode = 'sw';
   655    846   					{kind = 'vert', w = 5, h = 5;