diff --git a/crates/haku-wasm/src/lib.rs b/crates/haku-wasm/src/lib.rs index b45e065..416092f 100644 --- a/crates/haku-wasm/src/lib.rs +++ b/crates/haku-wasm/src/lib.rs @@ -279,11 +279,24 @@ extern "C" fn haku_status_string(code: StatusCode) -> *const i8 { .as_ptr() } +#[derive(Debug)] +struct CompileStats { + ast_size: usize, +} + +#[derive(Debug)] +struct RunnableBrush { + chunk_id: ChunkId, + closure_spec: ClosureSpec, + + compile_stats: CompileStats, +} + #[derive(Debug, Default)] enum BrushState { #[default] Default, - Ready(ChunkId, ClosureSpec), + Ready(RunnableBrush), } #[derive(Debug, Default)] @@ -431,7 +444,13 @@ unsafe extern "C" fn haku_compile_brush( Ok(chunk_id) => chunk_id, Err(_) => return StatusCode::TooManyChunks, }; - brush.state = BrushState::Ready(chunk_id, closure_spec); + brush.state = BrushState::Ready(RunnableBrush { + chunk_id, + closure_spec, + compile_stats: CompileStats { + ast_size: ast.len(), + }, + }); info!("brush compiled into {chunk_id:?}"); @@ -470,7 +489,7 @@ unsafe extern "C" fn haku_begin_brush(instance: *mut Instance, brush: *const Bru let instance = &mut *instance; let brush = &*brush; - let BrushState::Ready(chunk_id, closure_spec) = brush.state else { + let BrushState::Ready(runnable) = &brush.state else { panic!("brush is not compiled and ready to be used"); }; @@ -479,10 +498,10 @@ unsafe extern "C" fn haku_begin_brush(instance: *mut Instance, brush: *const Bru instance.reset_exception(); instance.trampoline = None; - let Ok(closure_id) = instance - .vm - .create_ref(Ref::Closure(Closure::chunk(chunk_id, closure_spec))) - else { + let Ok(closure_id) = instance.vm.create_ref(Ref::Closure(Closure::chunk( + runnable.chunk_id, + runnable.closure_spec, + ))) else { return StatusCode::OutOfRefSlots; }; @@ -574,3 +593,26 @@ unsafe extern "C" fn haku_cont_dotter( ) }) } + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_stat_ast_size(brush: *const Brush) -> usize { + match &(*brush).state { + BrushState::Default => 0, + BrushState::Ready(runnable) => runnable.compile_stats.ast_size, + } +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_stat_num_refs(instance: *const Instance) -> usize { + (*instance).vm.num_refs() +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_stat_remaining_fuel(instance: *const Instance) -> usize { + (*instance).vm.remaining_fuel() +} + +#[unsafe(no_mangle)] +unsafe extern "C" fn haku_stat_remaining_memory(instance: *const Instance) -> usize { + (*instance).vm.remaining_memory() +} diff --git a/crates/haku/src/vm.rs b/crates/haku/src/vm.rs index ce9df0e..59aa97b 100644 --- a/crates/haku/src/vm.rs +++ b/crates/haku/src/vm.rs @@ -73,10 +73,18 @@ impl Vm { } } + pub fn num_refs(&self) -> usize { + self.refs.len() + } + pub fn remaining_fuel(&self) -> usize { self.fuel } + pub fn remaining_memory(&self) -> usize { + self.memory + } + pub fn set_fuel(&mut self, fuel: usize) { self.fuel = fuel; } diff --git a/scripts/mkicon.fish b/scripts/mkicon.fish index 5fbca61..406f1b4 100755 --- a/scripts/mkicon.fish +++ b/scripts/mkicon.fish @@ -1,7 +1,13 @@ #!/usr/bin/env fish -set filename $argv[1] -set icon_name (basename $filename .svg) -set icon_base64 (svgcleaner --stdout $filename 2>/dev/null | base64 -w0) +function mkicon + set -l filename $argv[1] + set -l icon_name (basename $filename .svg) + set -l icon_base64 (svgcleaner --stdout $filename 2>/dev/null | base64 -w0) -printf "--icon-%s: url('data:image/svg+xml;base64,%s');" "$icon_name" "$icon_base64" + printf "--icon-%s: url('data:image/svg+xml;base64,%s');\n" "$icon_name" "$icon_base64" +end + +for arg in $argv + mkicon $arg +end diff --git a/static/base.css b/static/base.css index 933649d..5118f27 100644 --- a/static/base.css +++ b/static/base.css @@ -166,12 +166,20 @@ pre:has(code) { /* Icons */ :root { - --icon-rkgk-grayscale: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxnIGNsaXAtcGF0aD0idXJsKCNhKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Im0xMiAxNy00LjU1Mjc5LTkuMTA1NTdjLS42NjQ5LTEuMzI5ODEuMzAyMDktMi44OTQ0MyAxLjc4ODg2LTIuODk0NDNoOC43NjM5MyIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxnIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0ibTUuNSAxMi0yLjUgNSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW9wYWNpdHk9Ii41Ii8+PHBhdGggZD0ibTMgNWgxIi8+PC9nPjwvZz48L3N2Zz4="); + --icon-brackets: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNyA0aC0zdjhoM20yLThoM3Y4aC0zIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg=="); + --icon-droplet: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNSAxMGMwLTEuNjM0NzIgMS4wMDIxMi00LjI3MTU2IDIuMTg3MjYtNi41NDUzNy4zNDg5My0uNjY5NDQgMS4yNzY1NS0uNjY5NDQgMS42MjU0OCAwIDEuMTg1MTQgMi4yNzM4MSAyLjE4NzI2IDQuOTEwNjUgMi4xODcyNiA2LjU0NTM3IDAgMi0xLjUgMy0zIDNzLTMtMS0zLTN6IiBmaWxsPSIjMDAwIi8+PC9zdmc+"); --icon-external-link: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBzdHJva2U9IiMwMDAiPjxwYXRoIGQ9Im0xMC41IDl2Mi41aC02di02aDIuNSIvPjxwYXRoIGQ9Im03IDkgNC41LTQuNW0wIDBoLTMuNW0zLjUgMHYzLjUiLz48L2c+PC9zdmc+"); + --icon-memory: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjMDAwIiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iLjEiPjxwYXRoIGQ9Im01LjA1IDMuMDVoNS45djkuOWgtNS45eiIvPjxwYXRoIGQ9Im0zLjA1IDQuMDVoMS45djEuOWgtMS45eiIvPjxwYXRoIGQ9Im0xMS4wNSA0LjA1aDEuOXYxLjloLTEuOXoiLz48cGF0aCBkPSJtMy4wNSA3LjA1aDEuOXYxLjloLTEuOXoiLz48cGF0aCBkPSJtMTEuMDUgNy4wNWgxLjl2MS45aC0xLjl6Ii8+PHBhdGggZD0ibTMuMDUgMTAuMDVoMS45djEuOWgtMS45eiIvPjxwYXRoIGQ9Im0xMS4wNSAxMC4wNWgxLjl2MS45aC0xLjl6Ii8+PC9nPjwvc3ZnPg=="); + --icon-object: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0xMiA2YzAgMS4xMDQ1Ny0uODk1NCAyLTIgMnYyYzIuMjA5MSAwIDQtMS43OTA4NiA0LTRzLTEuNzkwOS00LTQtNGMtMi4yMDkxNCAwLTQgMS43OTA4Ni00IDRoMmMwLTEuMTA0NTcuODk1NDMtMiAyLTIgMS4xMDQ2IDAgMiAuODk1NDMgMiAyeiIgZmlsbD0iIzAwMCIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggZD0ibTMgN2g2djZoLTZ6IiBzdHJva2U9IiMwMDAiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg=="); + --icon-rkgk-grayscale: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIiB4bWxuczp4bGluaz0iaHR0cDovL3d3dy53My5vcmcvMTk5OS94bGluayI+PGNsaXBQYXRoIGlkPSJhIj48cGF0aCBkPSJtMCAwaDE2djE2aC0xNnoiLz48L2NsaXBQYXRoPjxnIGNsaXAtcGF0aD0idXJsKCNhKSIgc3Ryb2tlPSIjMDAwIiBzdHJva2Utd2lkdGg9IjIiPjxwYXRoIGQ9Im0xMiAxNy00LjU1Mjc5LTkuMTA1NTdjLS42NjQ5LTEuMzI5ODEuMzAyMDktMi44OTQ0MyAxLjc4ODg2LTIuODk0NDNoOC43NjM5MyIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIvPjxnIHN0cm9rZS1saW5lY2FwPSJyb3VuZCI+PHBhdGggZD0ibTUuNSAxMi0yLjUgNSIgc3Ryb2tlLWxpbmVqb2luPSJyb3VuZCIgc3Ryb2tlLW9wYWNpdHk9Ii41Ii8+PHBhdGggZD0ibTMgNWgxIi8+PC9nPjwvZz48L3N2Zz4="); + + --icon-brackets-white: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNyA0aC0zdjhoM20yLThoM3Y4aC0zIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg=="); + --icon-droplet-white: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBkPSJtNSAxMGMwLTEuNjM0NzIgMS4wMDIxMi00LjI3MTU2IDIuMTg3MjYtNi41NDUzNy4zNDg5My0uNjY5NDQgMS4yNzY1NS0uNjY5NDQgMS42MjU0OCAwIDEuMTg1MTQgMi4yNzM4MSAyLjE4NzI2IDQuOTEwNjUgMi4xODcyNiA2LjU0NTM3IDAgMi0xLjUgMy0zIDNzLTMtMS0zLTN6IiBmaWxsPSIjZmZmIi8+PC9zdmc+"); + --icon-object-white: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48cGF0aCBjbGlwLXJ1bGU9ImV2ZW5vZGQiIGQ9Im0xMiA2YzAgMS4xMDQ1Ny0uODk1NCAyLTIgMnYyYzIuMjA5MSAwIDQtMS43OTA4NiA0LTRzLTEuNzkwOS00LTQtNGMtMi4yMDkxNCAwLTQgMS43OTA4Ni00IDRoMmMwLTEuMTA0NTcuODk1NDMtMiAyLTIgMS4xMDQ2IDAgMiAuODk1NDMgMiAyeiIgZmlsbD0iI2ZmZiIgZmlsbC1ydWxlPSJldmVub2RkIi8+PHBhdGggZD0ibTMgN2g2djZoLTZ6IiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iMiIvPjwvc3ZnPg=="); + --icon-memory-white: url("data:image/svg+xml;base64,PHN2ZyBmaWxsPSJub25lIiBoZWlnaHQ9IjE2IiB2aWV3Qm94PSIwIDAgMTYgMTYiIHdpZHRoPSIxNiIgeG1sbnM9Imh0dHA6Ly93d3cudzMub3JnLzIwMDAvc3ZnIj48ZyBmaWxsPSIjZmZmIiBzdHJva2U9IiNmZmYiIHN0cm9rZS13aWR0aD0iLjEiPjxwYXRoIGQ9Im01LjA1IDMuMDVoNS45djkuOWgtNS45eiIvPjxwYXRoIGQ9Im0zLjA1IDQuMDVoMS45djEuOWgtMS45eiIvPjxwYXRoIGQ9Im0xMS4wNSA0LjA1aDEuOXYxLjloLTEuOXoiLz48cGF0aCBkPSJtMy4wNSA3LjA1aDEuOXYxLjloLTEuOXoiLz48cGF0aCBkPSJtMTEuMDUgNy4wNWgxLjl2MS45aC0xLjl6Ii8+PHBhdGggZD0ibTMuMDUgMTAuMDVoMS45djEuOWgtMS45eiIvPjxwYXRoIGQ9Im0xMS4wNSAxMC4wNWgxLjl2MS45aC0xLjl6Ii8+PC9nPjwvc3ZnPg=="); } .icon { - display: inline-block; vertical-align: middle; width: 16px; height: 16px; @@ -179,10 +187,35 @@ pre:has(code) { background-repeat: no-repeat; background-position: 50% 50%; - &.icon-rkgk-grayscale { - background-image: var(--icon-rkgk-grayscale); + &.icon-brackets { + background-image: var(--icon-brackets); + } + &.icon-droplet { + background-image: var(--icon-droplet); } &.icon-external-link { background-image: var(--icon-external-link); } + &.icon-memory { + background-image: var(--icon-memory); + } + &.icon-object { + background-image: var(--icon-object); + } + &.icon-rkgk-grayscale { + background-image: var(--icon-rkgk-grayscale); + } + + &.icon-brackets-white { + background-image: var(--icon-brackets-white); + } + &.icon-droplet-white { + background-image: var(--icon-droplet-white); + } + &.icon-memory-white { + background-image: var(--icon-memory-white); + } + &.icon-object-white { + background-image: var(--icon-object-white); + } } diff --git a/static/brush-cost.js b/static/brush-cost.js new file mode 100644 index 0000000..31e2263 --- /dev/null +++ b/static/brush-cost.js @@ -0,0 +1,81 @@ +class Gauge extends HTMLElement { + constructor(iconName, label) { + super(); + + this.iconName = iconName; + this.label = label; + } + + connectedCallback() { + this.role = "progressbar"; + this.classList.add("icon", `icon-${this.iconName}`); + + this.full = this.appendChild(document.createElement("div")); + this.full.classList.add("full", "icon", `icon-${this.iconName}-white`); + } + + setValue(value, valueMax) { + let clampedNormalized = Math.max(0, Math.min(1, value / valueMax)); + this.style.setProperty("--progress", `${clampedNormalized * 100}%`); + this.title = `${this.label}: ${value} / ${valueMax} (${Math.ceil((value / valueMax) * 100)}%)`; + } +} + +customElements.define("rkgk-brush-cost-gauge", Gauge); + +export class BrushCostGauges extends HTMLElement { + constructor() { + super(); + } + + connectedCallback() { + this.codeSizeGauge = this.appendChild( + createGauge({ + className: "code-size", + iconName: "brackets", + label: "Code size", + }), + ); + this.fuelGauge = this.appendChild( + createGauge({ + className: "fuel", + iconName: "droplet", + label: "Fuel", + }), + ); + this.objectsGauge = this.appendChild( + createGauge({ + className: "objects", + iconName: "object", + label: "Objects", + }), + ); + this.memoryGauge = this.appendChild( + createGauge({ + className: "memory", + iconName: "memory", + label: "Bulk memory", + }), + ); + + this.codeSizeGauge.setValue(0); + this.fuelGauge.setValue(0); + this.objectsGauge.setValue(0); + this.memoryGauge.setValue(0); + } + + update(stats) { + this.codeSizeGauge.setValue(stats.astSize, stats.astSizeMax); + this.fuelGauge.setValue(stats.fuel, stats.fuelMax); + this.objectsGauge.setValue(stats.numRefs, stats.numRefsMax); + this.memoryGauge.setValue(stats.memory, stats.memoryMax); + } +} + +customElements.define("rkgk-brush-cost-gauges", BrushCostGauges); + +function createGauge({ className, iconName, label }) { + let gauge = new Gauge(iconName, label); + gauge.classList.add(className); + return gauge; +} diff --git a/static/haku.js b/static/haku.js index efede63..2a734df 100644 --- a/static/haku.js +++ b/static/haku.js @@ -257,4 +257,24 @@ export class Haku { return { status: "ok" }; } + + get astSize() { + if (this.#pBrush != 0) { + return w.haku_stat_ast_size(this.#pBrush); + } else { + return 0; + } + } + + get numRefs() { + return w.haku_stat_num_refs(this.#pInstance); + } + + get remainingFuel() { + return w.haku_stat_remaining_fuel(this.#pInstance); + } + + get remainingMemory() { + return w.haku_stat_remaining_memory(this.#pInstance); + } } diff --git a/static/icon/brackets-white.svg b/static/icon/brackets-white.svg new file mode 100644 index 0000000..06e166d --- /dev/null +++ b/static/icon/brackets-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icon/brackets.svg b/static/icon/brackets.svg new file mode 100644 index 0000000..7404df9 --- /dev/null +++ b/static/icon/brackets.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icon/droplet-white.svg b/static/icon/droplet-white.svg new file mode 100644 index 0000000..ea06ed0 --- /dev/null +++ b/static/icon/droplet-white.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icon/droplet.svg b/static/icon/droplet.svg new file mode 100644 index 0000000..e17734d --- /dev/null +++ b/static/icon/droplet.svg @@ -0,0 +1,3 @@ + + + diff --git a/static/icon/memory-white.svg b/static/icon/memory-white.svg new file mode 100644 index 0000000..202e9e3 --- /dev/null +++ b/static/icon/memory-white.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/static/icon/memory.svg b/static/icon/memory.svg new file mode 100644 index 0000000..2e84fe3 --- /dev/null +++ b/static/icon/memory.svg @@ -0,0 +1,9 @@ + + + + + + + + + diff --git a/static/icon/object-white.svg b/static/icon/object-white.svg new file mode 100644 index 0000000..e270309 --- /dev/null +++ b/static/icon/object-white.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/icon/object.svg b/static/icon/object.svg new file mode 100644 index 0000000..9054acb --- /dev/null +++ b/static/icon/object.svg @@ -0,0 +1,4 @@ + + + + diff --git a/static/icon/rkgk design.zip b/static/icon/rkgk design.zip new file mode 100644 index 0000000..1921ee5 Binary files /dev/null and b/static/icon/rkgk design.zip differ diff --git a/static/index.css b/static/index.css index 1b67091..b13f26b 100644 --- a/static/index.css +++ b/static/index.css @@ -15,7 +15,7 @@ main { height: 100%; position: relative; - &>.fullscreen { + & > .fullscreen { width: 100%; height: 100%; max-width: 100%; @@ -25,23 +25,26 @@ main { top: 0; } - &>.panels { + & > .panels { --right-width: 384px; /* Overridden by JavaScript */ - + box-sizing: border-box; padding: 16px; display: grid; - grid-template-columns: [left] 1fr [right-resize] auto [right] minmax(0, var(--right-width)); + grid-template-columns: [left] 1fr [right-resize] auto [right] minmax( + 0, + var(--right-width) + ); /* Pass all events through. Children may receive events as normal. */ pointer-events: none; - &>* { + & > * { pointer-events: all; } - &>.right { + & > .right { grid-column: right / right; min-height: 0; @@ -53,29 +56,41 @@ main { pointer-events: none; - &>* { + & > * { min-width: 0; min-height: 0; } - &>rkgk-resize-handle { + & > rkgk-resize-handle { pointer-events: auto; } - &>.docked>rkgk-brush-editor { + & > .docked > rkgk-brush-editor { max-height: 100%; pointer-events: auto; } - &>.floating>rkgk-brush-preview { - width: 128px; - height: 128px; - pointer-events: auto; + & > .floating { + display: flex; + flex-direction: column; + + gap: 12px; + + & > rkgk-brush-preview { + width: 128px; + height: 128px; + pointer-events: auto; + } + + & > rkgk-brush-cost-gauges { + width: 100%; + pointer-events: auto; + } } } } - &>rkgk-canvas-renderer { + & > rkgk-canvas-renderer { width: 100%; height: 100%; position: absolute; @@ -83,7 +98,7 @@ main { top: 0; } - &>rkgk-reticle-renderer { + & > rkgk-reticle-renderer { width: 100%; height: 100%; position: absolute; @@ -92,7 +107,7 @@ main { overflow: hidden; } - &>#js-loading { + & > #js-loading { background-color: var(--color-panel-background); display: flex; @@ -111,7 +126,7 @@ rkgk-resize-handle { cursor: col-resize; - &>.visual { + & > .visual { width: 2px; height: 100%; background-color: var(--color-brand-blue); @@ -119,7 +134,8 @@ rkgk-resize-handle { opacity: 0%; } - &:hover>.visual, &.dragging>.visual { + &:hover > .visual, + &.dragging > .visual { opacity: 100%; } } @@ -130,7 +146,7 @@ rkgk-resize-handle { rkgk-canvas-renderer { display: block; - &>canvas { + & > canvas { display: block; } } @@ -142,7 +158,7 @@ rkgk-reticle-renderer { pointer-events: none; - &>.reticles { + & > .reticles { position: relative; } } @@ -153,15 +169,15 @@ rkgk-reticle-cursor { position: absolute; display: block; - &>.container { - &>.arrow { + & > .container { + & > .arrow { width: 24px; height: 24px; background-color: var(--color); clip-path: path("M 0,0 L 13,13 L 6,13 L 0,19 Z"); } - &>.nickname { + & > .nickname { position: absolute; top: 20px; left: 8px; @@ -188,7 +204,7 @@ rkgk-code-editor { overflow: auto; - &>.layer { + & > .layer { position: absolute; left: 0; top: 0; @@ -203,7 +219,7 @@ rkgk-code-editor { display: flex; flex-direction: column; - &>.line { + & > .line { flex-shrink: 0; white-space: pre-wrap; @@ -211,14 +227,14 @@ rkgk-code-editor { } } - &>.layer-gutter { + & > .layer-gutter { user-select: none; counter-reset: line; color: transparent; - &>.line { + & > .line { display: flex; flex-direction: row; @@ -241,27 +257,27 @@ rkgk-code-editor { } } - &>.layer:not(.layer-gutter) { + & > .layer:not(.layer-gutter) { margin-left: var(--gutter-width); width: calc(100% - var(--gutter-width)); } - &>.layer-error-squiggles { + & > .layer-error-squiggles { color: transparent; - &>.line { - &>.squiggle { + & > .line { + & > .squiggle { text-decoration: underline wavy black; text-decoration-skip-ink: none; } - &>.squiggle-error { + & > .squiggle-error { text-decoration-color: var(--color-error); } } } - &>textarea { + & > textarea { display: block; width: calc(100% - var(--gutter-width)); margin: 0; @@ -294,8 +310,8 @@ rkgk-brush-editor.rkgk-panel { gap: 4px; position: relative; - - &>.text-area { + + & > .text-area { display: block; width: 100%; margin: 0; @@ -306,18 +322,19 @@ rkgk-brush-editor.rkgk-panel { box-sizing: border-box; } - &>.errors:empty, &>.error-header:empty { + & > .errors:empty, + & > .error-header:empty { display: none; } - &>.error-header { + & > .error-header { margin: 0; margin-top: 0.5em; font-size: 1rem; color: var(--color-error); } - &>.errors { + & > .errors { margin: 0; color: var(--color-error); white-space: pre-wrap; @@ -334,13 +351,19 @@ rkgk-brush-preview { display: block; position: relative; - background: - repeating-conic-gradient(var(--checkerboard-light) 0% 25%, var(--checkerboard-dark) 0% 50%) + background: repeating-conic-gradient( + var(--checkerboard-light) 0% 25%, + var(--checkerboard-dark) 0% 50% + ) 50% 50% / var(--checkerboard-size) var(--checkerboard-size); border-radius: 4px; + & > canvas { + border-radius: 4px; + } + &.error { - &>canvas { + & > canvas { display: none; } @@ -354,10 +377,53 @@ rkgk-brush-preview { } } +/* Brush cost gauges */ + +rkgk-brush-cost-gauges, +rkgk-brush-cost-gauges.rkgk-panel { + --gauge-size: 20px; + + height: var(--gauge-size); + border-radius: 4px; + + display: flex; + flex-direction: row; + + overflow: clip; /* clip corners */ + + & > rkgk-brush-cost-gauge { + display: block; + height: var(--gauge-size); + flex-grow: 1; + + & > .full { + width: 100%; + height: 100%; + + clip-path: xywh(0 0 var(--progress) 100%); + + background-color: var(--gauge-color); + } + + &.code-size { + --gauge-color: var(--color-brand-blue); + } + &.fuel { + --gauge-color: #f44096; + } + &.objects { + --gauge-color: #fd9916; + } + &.memory { + --gauge-color: #5aca40; + } + } +} + /* Welcome screen */ rkgk-welcome { - &>dialog { + & > dialog { h3 { margin: 0.5rem 0; font-size: 2rem; @@ -369,7 +435,8 @@ rkgk-welcome { /* Connection status dialogs */ rkgk-connection-status { - &>dialog[name='logging-in-dialog'][open], &>dialog[name='disconnected-dialog'][open] { + & > dialog[name="logging-in-dialog"][open], + & > dialog[name="disconnected-dialog"][open] { border: none; outline: none; background: none; @@ -379,8 +446,8 @@ rkgk-connection-status { align-items: center; } - &>dialog[name='error-dialog'][open] { - & textarea[name='error-text'] { + & > dialog[name="error-dialog"][open] { + & textarea[name="error-text"] { box-sizing: border-box; width: 100%; resize: none; @@ -394,7 +461,7 @@ rkgk-connection-status { .menu-bar { --border-radius: 4px; - + display: flex; align-items: center; box-sizing: border-box; @@ -403,9 +470,9 @@ rkgk-connection-status { height: 24px; border-radius: var(--border-radius); - &>a { + & > a { display: block; - + color: var(--color-text); padding: 4px 8px; text-decoration: none; @@ -432,11 +499,10 @@ rkgk-connection-status { } } - &>hr { + & > hr { height: 100%; margin: 0; border: none; border-right: 1px solid var(--color-panel-border); } } - diff --git a/static/index.js b/static/index.js index 5434f61..58ac40e 100644 --- a/static/index.js +++ b/static/index.js @@ -18,6 +18,7 @@ let canvasRenderer = main.querySelector("rkgk-canvas-renderer"); let reticleRenderer = main.querySelector("rkgk-reticle-renderer"); let brushEditor = main.querySelector("rkgk-brush-editor"); let brushPreview = main.querySelector("rkgk-brush-preview"); +let brushCostGauges = main.querySelector("rkgk-brush-cost-gauges"); let welcome = main.querySelector("rkgk-welcome"); let connectionStatus = main.querySelector("rkgk-connection-status"); @@ -251,12 +252,15 @@ function readUrl(urlString) { let compileResult = currentUser.setBrush(brushEditor.code); brushEditor.renderHakuResult("Compilation", compileResult); + brushCostGauges.update(currentUser.getStats(session.wallInfo)); + if (compileResult.status != "ok") { brushPreview.setErrorFlag(); return; } brushPreview.renderBrush(currentUser.haku).then((previewResult) => { + brushCostGauges.update(currentUser.getStats(session.wallInfo)); if (previewResult.status == "error") { brushEditor.renderHakuResult( previewResult.phase == "eval" ? "Evaluation" : "Rendering", diff --git a/static/online-users.js b/static/online-users.js index 16f39a0..9424412 100644 --- a/static/online-users.js +++ b/static/online-users.js @@ -89,6 +89,19 @@ export class User { return false; } } + + getStats(wallInfo) { + return { + astSize: this.haku.astSize, + astSizeMax: wallInfo.hakuLimits.ast_capacity, + numRefs: this.haku.numRefs, + numRefsMax: wallInfo.hakuLimits.ref_capacity, + fuel: wallInfo.hakuLimits.fuel - this.haku.remainingFuel, + fuelMax: wallInfo.hakuLimits.fuel, + memory: wallInfo.hakuLimits.memory - this.haku.remainingMemory, + memoryMax: wallInfo.hakuLimits.memory, + }; + } } export class OnlineUsers extends EventTarget { diff --git a/template/index.hbs.html b/template/index.hbs.html index 4aeff19..f9d846a 100644 --- a/template/index.hbs.html +++ b/template/index.hbs.html @@ -21,6 +21,7 @@