Differences From
Artifact [e3ed80cb5c]:
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;