From 7db92b90ee67c6cbc9a23369ce0e0b627b9fecc3 Mon Sep 17 00:00:00 2001 From: Karsten Schmidt Date: Sun, 11 Aug 2019 19:06:40 +0100 Subject: [PATCH] perf(imgui): update comp hashing to use murmur hash vs toString, use ES6 Maps - hash() ~2x faster than String() - use ES6 Maps for IMGUI resource caches to avoid hash string conv (8-10x faster) --- packages/imgui/src/components/button.ts | 20 +++++------ packages/imgui/src/components/dial.ts | 12 +++---- packages/imgui/src/components/radial-menu.ts | 10 +++--- packages/imgui/src/components/ring.ts | 16 ++++----- packages/imgui/src/components/sliderh.ts | 22 +++++------- packages/imgui/src/components/sliderv.ts | 25 +++++-------- packages/imgui/src/components/textfield.ts | 11 +++--- packages/imgui/src/components/toggle.ts | 7 ++-- packages/imgui/src/components/xypad.ts | 8 ++--- packages/imgui/src/gui.ts | 38 ++++++++++---------- 10 files changed, 77 insertions(+), 92 deletions(-) diff --git a/packages/imgui/src/components/button.ts b/packages/imgui/src/components/button.ts index 88c61f8e09..24a9859cb6 100644 --- a/packages/imgui/src/components/button.ts +++ b/packages/imgui/src/components/button.ts @@ -1,6 +1,6 @@ import { pointInside, rect } from "@thi.ng/geom"; import { IShape } from "@thi.ng/geom-api"; -import { ReadonlyVec, ZERO2 } from "@thi.ng/vectors"; +import { hash, ReadonlyVec, ZERO2 } from "@thi.ng/vectors"; import { IGridLayout, LayoutBox, MouseButton } from "../api"; import { handleButtonKeys } from "../behaviors/button"; import { IMGUI } from "../gui"; @@ -18,13 +18,13 @@ export const buttonH = ( ) => { const theme = gui.theme; const { x, y, w, h } = isLayout(layout) ? layout.next() : layout; - const hash = String([x, y, w, h]); + const key = hash([x, y, w, h]); return buttonRaw( gui, id, - gui.resource(id, hash, () => rect([x, y], [w, h])), - hash, - gui.resource(id, "mat" + hash, () => [ + gui.resource(id, key, () => rect([x, y], [w, h])), + key, + gui.resource(id, "mat" + key, () => [ 1, 0, 0, @@ -49,13 +49,13 @@ export const buttonV = ( ) => { const theme = gui.theme; const { x, y, w, h } = isLayout(layout) ? layout.next([1, rows]) : layout; - const hash = String([x, y, w, h]); + const key = hash([x, y, w, h]); return buttonRaw( gui, id, - gui.resource(id, hash, () => rect([x, y], [w, h])), - hash, - gui.resource(id, "mat" + hash, () => [ + gui.resource(id, key, () => rect([x, y], [w, h])), + key, + gui.resource(id, "mat" + key, () => [ 0, -1, 1, @@ -73,7 +73,7 @@ export const buttonRaw = ( gui: IMGUI, id: string, shape: IShape, - hash: string, + hash: number | string, lmat?: ReadonlyVec, label?: string, labelHover?: string, diff --git a/packages/imgui/src/components/dial.ts b/packages/imgui/src/components/dial.ts index 05d7767781..fffaa92a78 100644 --- a/packages/imgui/src/components/dial.ts +++ b/packages/imgui/src/components/dial.ts @@ -7,7 +7,7 @@ import { PI, TAU } from "@thi.ng/math"; -import { cartesian2 } from "@thi.ng/vectors"; +import { cartesian2, hash } from "@thi.ng/vectors"; import { LayoutBox, MouseButton } from "../api"; import { dialVal } from "../behaviors/dial"; import { handleSlider1Keys } from "../behaviors/slider"; @@ -70,8 +70,8 @@ export const dialRaw = ( ) => { const r = Math.min(w, h) / 2; const pos = [x + w / 2, y + h / 2]; - const hash = String([x, y, r]); - gui.registerID(id, hash); + const key = hash([x, y, r]); + gui.registerID(id, key); const hover = pointInCircle(gui.mouse, pos, r); let active = false; const thetaGap = PI / 2; @@ -79,7 +79,7 @@ export const dialRaw = ( if (hover) { gui.hotID = id; const aid = gui.activeID; - if ((aid === "" || aid === id) && gui.buttons == MouseButton.LEFT) { + if ((aid === "" || aid === id) && gui.buttons & MouseButton.LEFT) { gui.activeID = id; active = true; val[i] = dialVal( @@ -97,10 +97,10 @@ export const dialRaw = ( } const focused = gui.requestFocus(id); const v = val[i]; - const bgShape = gui.resource(id, hash, () => circle(pos, r, {})); + const bgShape = gui.resource(id, key, () => circle(pos, r, {})); bgShape.attribs.fill = gui.bgColor(hover || focused); bgShape.attribs.stroke = gui.focusColor(id); - const valShape = gui.resource(id, String(v), () => + const valShape = gui.resource(id, v, () => line( cartesian2( null, diff --git a/packages/imgui/src/components/radial-menu.ts b/packages/imgui/src/components/radial-menu.ts index f1854afb2f..e553a42e27 100644 --- a/packages/imgui/src/components/radial-menu.ts +++ b/packages/imgui/src/components/radial-menu.ts @@ -7,7 +7,7 @@ import { } from "@thi.ng/geom"; import { triFan } from "@thi.ng/geom-tessellate"; import { map } from "@thi.ng/transducers"; -import { add2, Vec } from "@thi.ng/vectors"; +import { add2, hash, Vec } from "@thi.ng/vectors"; import { IMGUI } from "../gui"; import { buttonRaw } from "./button"; @@ -21,9 +21,9 @@ export const radialMenu = ( info: string[] ) => { const n = items.length; - const hash = String([x, y, r, n]); - gui.registerID(id, hash); - const cells: [Polygon, Vec][] = gui.resource(id, hash, () => [ + const key = hash([x, y, r, n]); + gui.registerID(id, key); + const cells: [Polygon, Vec][] = gui.resource(id, key, () => [ ...map((pts) => { const cell = polygon(pts); return [cell, centroid(cell)]; @@ -42,7 +42,7 @@ export const radialMenu = ( gui, id + i, cell[0], - String(p), + hash(p), [1, 0, 0, 1, p[0], p[1]], items[i], undefined, diff --git a/packages/imgui/src/components/ring.ts b/packages/imgui/src/components/ring.ts index 5b9a79f6c7..099d626aab 100644 --- a/packages/imgui/src/components/ring.ts +++ b/packages/imgui/src/components/ring.ts @@ -10,8 +10,8 @@ import { TAU } from "@thi.ng/math"; import { map, normRange } from "@thi.ng/transducers"; -import { cartesian2, Vec } from "@thi.ng/vectors"; -import { KeyModifier, MouseButton } from "../api"; +import { cartesian2, hash, Vec } from "@thi.ng/vectors"; +import { MouseButton } from "../api"; import { dialVal } from "../behaviors/dial"; import { handleSlider1Keys } from "../behaviors/slider"; import { IMGUI } from "../gui"; @@ -96,8 +96,8 @@ export const ringRaw = ( info?: string ) => { const r = w / 2; - const hash = String([x, y, r]); - gui.registerID(id, hash); + const key = hash([x, y, r]); + gui.registerID(id, key); const pos = [x + r, y + r]; const hover = pointInRect(gui.mouse, [x, y], [w, h]); let active = false; @@ -118,9 +118,7 @@ export const ringRaw = ( max, prec ); - if (gui.modifiers & KeyModifier.ALT) { - val.fill(val[i]); - } + gui.isAltDown() && val.fill(val[i]); } info && tooltipRaw(gui, info); } @@ -130,7 +128,7 @@ export const ringRaw = ( const r2 = r * rscale; // adaptive arc resolution const res = fitClamped(r, 15, 80, 12, 30); - const bgShape = gui.resource(id, hash, () => + const bgShape = gui.resource(id, key, () => polygon( [ ...arcVerts(pos, r, startTheta, endTheta, res), @@ -141,7 +139,7 @@ export const ringRaw = ( ); bgShape.attribs.fill = gui.bgColor(hover || focused); bgShape.attribs.stroke = gui.focusColor(id); - const valShape = gui.resource(id, String(v), () => + const valShape = gui.resource(id, v, () => polygon( [ ...arcVerts(pos, r, startTheta, valTheta, res), diff --git a/packages/imgui/src/components/sliderh.ts b/packages/imgui/src/components/sliderh.ts index 89d2a46e9e..0de4aeffdb 100644 --- a/packages/imgui/src/components/sliderh.ts +++ b/packages/imgui/src/components/sliderh.ts @@ -1,12 +1,8 @@ import { Fn } from "@thi.ng/api"; import { pointInside, rect } from "@thi.ng/geom"; import { fit, norm } from "@thi.ng/math"; -import { - IGridLayout, - KeyModifier, - LayoutBox, - MouseButton -} from "../api"; +import { hash } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox, MouseButton } from "../api"; import { handleSlider1Keys, slider1Val } from "../behaviors/slider"; import { IMGUI } from "../gui"; import { isLayout } from "../layout"; @@ -71,15 +67,15 @@ export const sliderHRaw = ( info?: string ) => { const theme = gui.theme; - const hash = String([x, y, w, h]); - gui.registerID(id, hash); - const box = gui.resource(id, hash, () => rect([x, y], [w, h], {})); + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h], {})); const hover = pointInside(box, gui.mouse); let active = false; if (hover) { gui.hotID = id; const aid = gui.activeID; - if ((aid === "" || aid === id) && gui.buttons == MouseButton.LEFT) { + if ((aid === "" || aid === id) && gui.buttons & MouseButton.LEFT) { gui.activeID = id; active = true; val[i] = slider1Val( @@ -88,16 +84,14 @@ export const sliderHRaw = ( max, prec ); - if (gui.modifiers & KeyModifier.ALT) { - val.fill(val[i]); - } + gui.isAltDown() && val.fill(val[i]); } info && tooltipRaw(gui, info); } const focused = gui.requestFocus(id); const v = val[i]; const normVal = norm(v, min, max); - const valueBox = gui.resource(id, String(v), () => + const valueBox = gui.resource(id, v, () => rect([x, y], [1 + normVal * (w - 1), h], {}) ); valueBox.attribs.fill = gui.fgColor(hover); diff --git a/packages/imgui/src/components/sliderv.ts b/packages/imgui/src/components/sliderv.ts index 7df3e78b22..ea5b2be899 100644 --- a/packages/imgui/src/components/sliderv.ts +++ b/packages/imgui/src/components/sliderv.ts @@ -1,13 +1,8 @@ import { Fn } from "@thi.ng/api"; import { pointInside, rect } from "@thi.ng/geom"; import { fit, norm } from "@thi.ng/math"; -import { ZERO2 } from "@thi.ng/vectors"; -import { - IGridLayout, - KeyModifier, - LayoutBox, - MouseButton -} from "../api"; +import { hash, ZERO2 } from "@thi.ng/vectors"; +import { IGridLayout, LayoutBox, MouseButton } from "../api"; import { handleSlider1Keys, slider1Val } from "../behaviors/slider"; import { IMGUI } from "../gui"; import { isLayout } from "../layout"; @@ -73,16 +68,16 @@ export const sliderVRaw = ( info?: string ) => { const theme = gui.theme; - const hash = String([x, y, w, h]); - gui.registerID(id, hash); - const box = gui.resource(id, hash, () => rect([x, y], [w, h], {})); + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h], {})); const hover = pointInside(box, gui.mouse); const ymax = y + h; let active = false; if (hover) { gui.hotID = id; const aid = gui.activeID; - if ((aid === "" || aid === id) && gui.buttons == MouseButton.LEFT) { + if ((aid === "" || aid === id) && gui.buttons & MouseButton.LEFT) { gui.activeID = id; active = true; val[i] = slider1Val( @@ -91,16 +86,14 @@ export const sliderVRaw = ( max, prec ); - if (gui.modifiers & KeyModifier.ALT) { - val.fill(val[i]); - } + gui.isAltDown() && val.fill(val[i]); } info && tooltipRaw(gui, info); } const focused = gui.requestFocus(id); const v = val[i]; const normVal = norm(v, min, max); - const valueBox = gui.resource(id, String(v), () => { + const valueBox = gui.resource(id, v, () => { const nh = normVal * (h - 1); return rect([x, ymax - nh], [w, nh], {}); }); @@ -119,7 +112,7 @@ export const sliderVRaw = ( x + w / 2 + theme.baseLine, ymax - theme.pad ], - fill: gui.textColor(normVal > 0.25) + fill: gui.textColor(false) }, (label ? label + " " : "") + (fmt ? fmt(v) : v) ) diff --git a/packages/imgui/src/components/textfield.ts b/packages/imgui/src/components/textfield.ts index da9ace25f5..8d42e99534 100644 --- a/packages/imgui/src/components/textfield.ts +++ b/packages/imgui/src/components/textfield.ts @@ -1,6 +1,7 @@ import { Predicate } from "@thi.ng/api"; import { pointInside, rect } from "@thi.ng/geom"; import { fitClamped } from "@thi.ng/math"; +import { hash } from "@thi.ng/vectors"; import { IGridLayout, Key, @@ -44,16 +45,14 @@ export const textFieldRaw = ( const maxOffset = Math.max(0, txtLen - maxLen); const offset = label[2] || 0; const drawTxt = txt.substr(offset, maxLen); - const hash = String([x, y, w, h]); - gui.registerID(id, hash); - const box = gui.resource(id, hash, () => rect([x, y], [w, h], {})); + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h], {})); const hover = pointInside(box, gui.mouse); if (hover) { gui.hotID = id; if (gui.buttons & MouseButton.LEFT) { - if (gui.activeID === "") { - gui.activeID = id; - } + gui.activeID === "" && (gui.activeID = id); label[1] = Math.min( Math.round( fitClamped( diff --git a/packages/imgui/src/components/toggle.ts b/packages/imgui/src/components/toggle.ts index c2ce6f2a5d..3867f9ddde 100644 --- a/packages/imgui/src/components/toggle.ts +++ b/packages/imgui/src/components/toggle.ts @@ -1,4 +1,5 @@ import { pointInside, rect } from "@thi.ng/geom"; +import { hash } from "@thi.ng/vectors"; import { IGridLayout, LayoutBox, MouseButton } from "../api"; import { handleButtonKeys } from "../behaviors/button"; import { IMGUI } from "../gui"; @@ -59,9 +60,9 @@ export const toggleRaw = ( info?: string ) => { const theme = gui.theme; - const hash = String([x, y, w, h]); - gui.registerID(id, hash); - const box = gui.resource(id, hash, () => rect([x, y], [w, h])); + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h])); const hover = pointInside(box, gui.mouse); if (hover) { gui.hotID = id; diff --git a/packages/imgui/src/components/xypad.ts b/packages/imgui/src/components/xypad.ts index fc6b87d5fd..4ce69d651b 100644 --- a/packages/imgui/src/components/xypad.ts +++ b/packages/imgui/src/components/xypad.ts @@ -1,6 +1,6 @@ import { Fn } from "@thi.ng/api"; import { line, pointInside, rect } from "@thi.ng/geom"; -import { fit2, Vec } from "@thi.ng/vectors"; +import { fit2, hash, Vec } from "@thi.ng/vectors"; import { IGridLayout, LayoutBox, MouseButton } from "../api"; import { handleSlider2Keys, slider2Val } from "../behaviors/slider"; import { IMGUI } from "../gui"; @@ -94,9 +94,9 @@ export const xyPadRaw = ( const maxY = y + h - 1; const pos = yUp ? [x, maxY] : [x, y]; const maxPos = yUp ? [maxX, y] : [maxX, y + h - 1]; - const hash = String([x, y, w, h]); - gui.registerID(id, hash); - const box = gui.resource(id, hash, () => rect([x, y], [w, h])); + const key = hash([x, y, w, h]); + gui.registerID(id, key); + const box = gui.resource(id, key, () => rect([x, y], [w, h])); const col = gui.textColor(false); const hover = pointInside(box, gui.mouse); let active = false; diff --git a/packages/imgui/src/gui.ts b/packages/imgui/src/gui.ts index 23aed7c248..ccef006827 100644 --- a/packages/imgui/src/gui.ts +++ b/packages/imgui/src/gui.ts @@ -1,4 +1,4 @@ -import { Fn0, IObjectOf, IToHiccup } from "@thi.ng/api"; +import { Fn0, IToHiccup } from "@thi.ng/api"; import { setC2, Vec } from "@thi.ng/vectors"; import { DEFAULT_THEME, @@ -34,9 +34,9 @@ export class IMGUI implements IToHiccup { protected currIDs: Set; protected prevIDs: Set; - protected resources: IObjectOf; - protected states: IObjectOf; - protected sizes: IObjectOf; + protected resources: Map>; + protected states: Map; + protected sizes: Map; constructor(opts: IMGUIOpts) { this.width = opts.width; @@ -49,9 +49,9 @@ export class IMGUI implements IToHiccup { this.hotID = this.activeID = this.focusID = this.lastID = ""; this.currIDs = new Set(); this.prevIDs = new Set(); - this.resources = {}; - this.sizes = {}; - this.states = {}; + this.resources = new Map>(); + this.sizes = new Map(); + this.states = new Map(); this.layers = [[], []]; const touchActive = (e: TouchEvent) => { setMouse(e, this.mouse); @@ -109,8 +109,6 @@ export class IMGUI implements IToHiccup { setTheme(theme: Partial) { this.theme = { ...DEFAULT_THEME, ...theme }; - this.sizes = {}; - this.resources = {}; this.updateAttribs(); } @@ -169,9 +167,9 @@ export class IMGUI implements IToHiccup { const curr = this.currIDs; for (let id of prev) { if (!curr.has(id)) { - delete this.resources[id]; - delete this.sizes[id]; - delete this.states[id]; + this.resources.delete(id); + this.sizes.delete(id); + this.states.delete(id); } } this.prevIDs = curr; @@ -199,17 +197,19 @@ export class IMGUI implements IToHiccup { return this.theme.charWidth * txt.length; } - registerID(id: string, hash = "") { + registerID(id: string, hash: number | string) { this.currIDs.add(id); - if (this.sizes[id] !== hash) { - this.sizes[id] = hash; - delete this.resources[id]; + if (this.sizes.get(id) !== hash) { + this.sizes.set(id, hash); + this.resources.delete(id); } } - resource(id: string, hash: string, ctor: Fn0) { - const c = this.resources[id] || (this.resources[id] = {}); - return c[hash] || (c[hash] = ctor()); + resource(id: string, hash: number | string, ctor: Fn0) { + let res: any; + let c = this.resources.get(id); + !c && this.resources.set(id, (c = new Map())); + return c.get(hash) || (c.set(hash, (res = ctor())), res); } add(...els: any[]) {