diff --git a/web/static/src/ts/core/Widget.ts b/web/static/src/ts/core/Widget.ts index d65da5ca..7f4d4485 100644 --- a/web/static/src/ts/core/Widget.ts +++ b/web/static/src/ts/core/Widget.ts @@ -15,7 +15,7 @@ let wl: any[] = []; (window).wl = wl; interface Meta { - id: number; + readonly id: number; // name: string; // template: string; vnode: VNode | null; diff --git a/web/static/src/ts/core/utils.ts b/web/static/src/ts/core/utils.ts index 74cb005b..06ff89d8 100644 --- a/web/static/src/ts/core/utils.ts +++ b/web/static/src/ts/core/utils.ts @@ -31,3 +31,23 @@ export function idGenerator(): (() => number) { let nextID = 1; return () => nextID++; } + +export type HashFn = (args: any[]) => string; + +export function memoize R>( + f: T, + hash?: HashFn +): T { + if (!hash) { + hash = args => args.map(a => String(a)).join(","); + } + let cache: { [key: string]: R } = {}; + function memoizedFunction(...args: any[]) { + let hashValue = hash!(args); + if (!(hashValue in cache)) { + cache[hashValue] = f(...args); + } + return cache[hashValue]; + } + return memoizedFunction as T; +} diff --git a/web/static/src/ts/env.ts b/web/static/src/ts/env.ts index 68082760..41eb94ce 100644 --- a/web/static/src/ts/env.ts +++ b/web/static/src/ts/env.ts @@ -1,5 +1,5 @@ import { QWeb } from "./core/qweb_vdom"; -import { idGenerator } from "./core/utils"; +import { idGenerator, memoize } from "./core/utils"; import { WEnv } from "./core/widget"; import { ActionManager, IActionManager } from "./services/action_manager"; import { Ajax, IAjax } from "./services/ajax"; @@ -32,7 +32,17 @@ export interface Env extends WEnv { // Code //------------------------------------------------------------------------------ -export function makeEnvironment(): Env { +/** + * makeEnvironment returns the main environment for the application. + * + * Note that it does not make much sense (except for tests) to have more than + * one environment. For example, with two environment, the router code in one + * environment will probably interfere with the code from the other environment. + * + * For this reason, the result of makeEnvironment is memoized: every call to + * this function will actually return the same environment. + */ +export const makeEnvironment = memoize(function(): Env { const qweb = new QWeb(); const router = new Router(); const ajax = new Ajax(); @@ -55,4 +65,4 @@ export function makeEnvironment(): Env { rpc: ajax.rpc, debug: false }; -} +}); diff --git a/web/static/tests/core/utils.test.ts b/web/static/tests/core/utils.test.ts index e5374adb..443b4fa3 100644 --- a/web/static/tests/core/utils.test.ts +++ b/web/static/tests/core/utils.test.ts @@ -1,4 +1,9 @@ -import { escape, htmlTrim, idGenerator } from "../../src/ts/core/utils"; +import { + escape, + htmlTrim, + idGenerator, + memoize +} from "../../src/ts/core/utils"; describe("escape", () => { test("normal strings", () => { @@ -34,3 +39,23 @@ describe("idGenerator", () => { expect(gen()).toBe(3); }); }); + +describe("memoize", () => { + test("return correct value", () => { + const f = memoize((a, b) => a + b); + expect(f(1, 3)).toBe(4); + }); + + test("does not recompute if not needed", () => { + let nCalls = 0; + function origFunction(a: number, b: number): number { + nCalls++; + return a + b; + } + const memoized = memoize(origFunction); + + expect(memoized(1, 3)).toBe(4); + expect(memoized(1, 3)).toBe(4); + expect(nCalls).toBe(1); + }); +});