From 092fd990993b847ca26a5a814b2934c3752cac39 Mon Sep 17 00:00:00 2001 From: Kris Selden Date: Fri, 15 Jul 2016 20:35:46 -0700 Subject: [PATCH] Add more flexibility to Cache and use it for caching in the glimmer Environment. --- package.json | 2 +- packages/ember-glimmer/lib/environment.js | 108 +++++++++++------- .../lib/syntax/curly-component.js | 48 ++++---- packages/ember-glimmer/lib/syntax/outlet.js | 10 +- packages/ember-glimmer/lib/syntax/render.js | 6 +- packages/ember-metal/lib/cache.js | 66 +++++++---- 6 files changed, 140 insertions(+), 100 deletions(-) diff --git a/package.json b/package.json index eed95809272..5e32844cdd7 100644 --- a/package.json +++ b/package.json @@ -37,7 +37,7 @@ "express": "^4.5.0", "finalhandler": "^0.4.0", "github": "^0.2.3", - "glimmer-engine": "tildeio/glimmer#d2feca1", + "glimmer-engine": "tildeio/glimmer#592445", "glob": "^5.0.13", "htmlbars": "0.14.24", "mocha": "^2.4.5", diff --git a/packages/ember-glimmer/lib/environment.js b/packages/ember-glimmer/lib/environment.js index 12c6dbf0110..d0fd73af6ed 100644 --- a/packages/ember-glimmer/lib/environment.js +++ b/packages/ember-glimmer/lib/environment.js @@ -3,9 +3,10 @@ import { Environment as GlimmerEnvironment, HelperSyntax, AttributeChangeList, - isSafeString + isSafeString, + compileLayout } from 'glimmer-runtime'; -import Dict from 'ember-metal/empty_object'; +import Cache from 'ember-metal/cache'; import { assert, warn } from 'ember-metal/debug'; import { CurlyComponentSyntax, CurlyComponentDefinition } from './syntax/curly-component'; import { DynamicComponentSyntax } from './syntax/dynamic-component'; @@ -142,41 +143,60 @@ export default class Environment extends GlimmerEnvironment { constructor({ dom, [OWNER]: owner }) { super(dom); this.owner = owner; - this._components = new Dict(); - this._templateCache = new Dict(); + + this._definitionCache = new Cache(2000, ({ name, source }) => { + let { component: ComponentClass, layout } = lookupComponent(owner, name, { source }); + if (ComponentClass || layout) { + return new CurlyComponentDefinition(name, ComponentClass, layout); + } + }, ({ name, source }) => { + return source && owner._resolveLocalLookupName(name, source) || name; + }); + + this._templateCache = new Cache(1000, Template => { + return new Template(this); + }, template => template.id); + + this._compilerCache = new Cache(10, Compiler => { + return new Cache(2000, template => { + let compilable = new Compiler(template); + return compileLayout(compilable, this); + }, template => template.id); + }, Compiler => Compiler.id); + this.builtInModifiers = { action: new ActionModifierManager() }; } - // Hello future traveler, welcome to the world of syntax refinement. - // The method below is called by Glimmer's runtime compiler to allow - // us to take generic statement syntax and refine it to more meaniful - // syntax for Ember's use case. This on the fly switch-a-roo sounds fine - // and dandy, however Ember has precedence on statement refinement that you - // need to be aware of. The presendence for language constructs is as follows: - // - // ------------------------ - // Native & Built-in Syntax - // ------------------------ - // User-land components - // ------------------------ - // User-land helpers - // ------------------------ - // - // The one caveat here is that Ember also allows for dashed references that are - // not a component or helper: - // - // export default Component.extend({ - // 'foo-bar': 'LAME' - // }); - // - // {{foo-bar}} - // - // The heuristic for the above situation is a dashed "key" in inline form - // that does not resolve to a defintion. In this case refine statement simply - // isn't going to return any syntax and the Glimmer engine knows how to handle - // this case. + // Hello future traveler, welcome to the world of syntax refinement. + // The method below is called by Glimmer's runtime compiler to allow + // us to take generic statement syntax and refine it to more meaniful + // syntax for Ember's use case. This on the fly switch-a-roo sounds fine + // and dandy, however Ember has precedence on statement refinement that you + // need to be aware of. The presendence for language constructs is as follows: + // + // ------------------------ + // Native & Built-in Syntax + // ------------------------ + // User-land components + // ------------------------ + // User-land helpers + // ------------------------ + // + // The one caveat here is that Ember also allows for dashed references that are + // not a component or helper: + // + // export default Component.extend({ + // 'foo-bar': 'LAME' + // }); + // + // {{foo-bar}} + // + // The heuristic for the above situation is a dashed "key" in inline form + // that does not resolve to a defintion. In this case refine statement simply + // isn't going to return any syntax and the Glimmer engine knows how to handle + // this case. refineStatement(statement, parentMeta) { // 1. resolve any native syntax – if, unless, with, each, and partial @@ -244,20 +264,22 @@ export default class Environment extends GlimmerEnvironment { } getComponentDefinition(path, parentMeta) { - let source = parentMeta && `template:${parentMeta.moduleName}`; let name = path[0]; - let cacheKey = (source && this.owner._resolveLocalLookupName(name, source)) || name; - let definition = this._components[cacheKey]; - - if (!definition) { - let { component: ComponentClass, layout } = lookupComponent(this.owner, name, { source }); + let source = parentMeta && `template:${parentMeta.moduleName}`; + return this._definitionCache.get({ name, source }); + } - if (ComponentClass || layout) { - definition = this._components[cacheKey] = new CurlyComponentDefinition(name, ComponentClass, layout); - } - } + // normally templates should be exported at the proper module name + // and cached in the container, but this cache supports templates + // that have been set directly on the component's layout property + getTemplate(Template) { + return this._templateCache.get(Template); + } - return definition; + // a Compiler can wrap the template so it needs its own cache + getCompiledBlock(Compiler, template) { + let compilerCache = this._compilerCache.get(Compiler); + return compilerCache.get(template); } hasPartial(name) { diff --git a/packages/ember-glimmer/lib/syntax/curly-component.js b/packages/ember-glimmer/lib/syntax/curly-component.js index fad1249f421..e724bbc154e 100644 --- a/packages/ember-glimmer/lib/syntax/curly-component.js +++ b/packages/ember-glimmer/lib/syntax/curly-component.js @@ -1,9 +1,8 @@ -import { StatementSyntax, ValueReference, compileLayout } from 'glimmer-runtime'; +import { StatementSyntax, ValueReference } from 'glimmer-runtime'; import { TO_ROOT_REFERENCE, AttributeBindingReference, applyClassNameBinding } from '../utils/references'; import { DIRTY_TAG, IS_DISPATCHING_ATTRS, HAS_BLOCK } from '../component'; import { assert } from 'ember-metal/debug'; import processArgs from '../utils/process-args'; -import { getOwner } from 'container/owner'; import { privatize as P } from 'container/registry'; import get from 'ember-metal/property_get'; import { ComponentDefinition } from 'glimmer-runtime'; @@ -128,34 +127,28 @@ class CurlyComponentManager { } layoutFor(definition, bucket, env) { - if (definition.template) { - return compileLayout(new CurlyComponentLayoutCompiler(definition.template), env); + let template = definition.template; + if (!template) { + let { component } = bucket; + template = this.templateFor(component, env); } + return env.getCompiledBlock(CurlyComponentLayoutCompiler, template); + } - let { component } = bucket; - let template; - let TemplateFactory = component.layout; - // seen the definition but not the template - if (TemplateFactory) { - if (env._templateCache[TemplateFactory.id]) { - template = env._templateCache[TemplateFactory.id]; - } else { - template = new TemplateFactory(env); - env._templateCache[TemplateFactory.id] = template; - } - } else { - let layoutName = component.layoutName && get(component, 'layoutName'); - let owner = getOwner(component); - - if (layoutName) { - template = owner.lookup('template:' + layoutName); - } - if (!template) { - template = owner.lookup(DEFAULT_LAYOUT); + templateFor(component, env) { + let Template = component.layout; + if (Template) { + return env.getTemplate(Template); + } + let { owner } = env; + let layoutName = get(component, 'layoutName'); + if (layoutName) { + let template = owner.lookup('template:' + layoutName); + if (template) { + return template; } } - - return compileLayout(new CurlyComponentLayoutCompiler(template), env); + return owner.lookup(DEFAULT_LAYOUT); } getSelf({ component }) { @@ -254,7 +247,6 @@ export class CurlyComponentDefinition extends ComponentDefinition { constructor(name, ComponentClass, template) { super(name, MANAGER, ComponentClass || Component); this.template = template; - this._cache = undefined; } } @@ -271,3 +263,5 @@ class CurlyComponentLayoutCompiler { builder.attrs.static('class', 'ember-view'); } } + +CurlyComponentLayoutCompiler.id = 'curly'; diff --git a/packages/ember-glimmer/lib/syntax/outlet.js b/packages/ember-glimmer/lib/syntax/outlet.js index 36810f545ce..d6878a08567 100644 --- a/packages/ember-glimmer/lib/syntax/outlet.js +++ b/packages/ember-glimmer/lib/syntax/outlet.js @@ -1,4 +1,4 @@ -import { ArgsSyntax, StatementSyntax, compileLayout } from 'glimmer-runtime'; +import { ArgsSyntax, StatementSyntax } from 'glimmer-runtime'; import { generateGuid, guidFor } from 'ember-metal/utils'; import { RootReference } from '../utils/references'; @@ -124,7 +124,7 @@ class TopLevelOutletComponentManager extends AbstractOutletComponentManager { } layoutFor(definition, bucket, env) { - return compileLayout(new TopLevelOutletLayoutCompiler(definition.template), env); + return env.getCompiledBlock(TopLevelOutletLayoutCompiler, definition.template); } } @@ -139,7 +139,7 @@ class OutletComponentManager extends AbstractOutletComponentManager { } layoutFor(definition, bucket, env) { - return compileLayout(new OutletLayoutCompiler(definition.template), env); + return env.getCompiledBlock(OutletLayoutCompiler, definition.template); } } @@ -175,6 +175,8 @@ class TopLevelOutletLayoutCompiler { } } +TopLevelOutletLayoutCompiler.id = 'top-level-outlet'; + class OutletComponentDefinition extends AbstractOutletComponentDefinition { constructor(outletName, template) { super(MANAGER, outletName, template); @@ -190,3 +192,5 @@ class OutletLayoutCompiler { builder.wrapLayout(this.template.asLayout()); } } + +OutletLayoutCompiler.id = 'outlet'; diff --git a/packages/ember-glimmer/lib/syntax/render.js b/packages/ember-glimmer/lib/syntax/render.js index 1cb06d759e8..da1543f146c 100644 --- a/packages/ember-glimmer/lib/syntax/render.js +++ b/packages/ember-glimmer/lib/syntax/render.js @@ -1,4 +1,4 @@ -import { ArgsSyntax, StatementSyntax, compileLayout } from 'glimmer-runtime'; +import { ArgsSyntax, StatementSyntax } from 'glimmer-runtime'; import { ConstReference, isConst } from 'glimmer-reference'; import { assert } from 'ember-metal/debug'; import { RootReference } from '../utils/references'; @@ -58,7 +58,7 @@ class AbstractRenderManager { /* abstract create(definition, args, dynamicScope); */ layoutFor(definition, bucket, env) { - return compileLayout(new RenderLayoutCompiler(definition.template), env); + return env.getCompiledBlock(RenderLayoutCompiler, definition.template); } getSelf({ controller }) { @@ -137,3 +137,5 @@ class RenderLayoutCompiler { builder.wrapLayout(this.template.asLayout()); } } + +RenderLayoutCompiler.id = 'render'; diff --git a/packages/ember-metal/lib/cache.js b/packages/ember-metal/lib/cache.js index e169077ab84..ace9aefe31c 100644 --- a/packages/ember-metal/lib/cache.js +++ b/packages/ember-metal/lib/cache.js @@ -1,37 +1,35 @@ import EmptyObject from 'ember-metal/empty_object'; -export default Cache; - -function Cache(limit, func) { - this.store = new EmptyObject(); - this.size = 0; - this.misses = 0; - this.hits = 0; - this.limit = limit; - this.func = func; -} -function UNDEFINED() {} +export default class Cache { + constructor(limit, func, key, store) { + this.size = 0; + this.misses = 0; + this.hits = 0; + this.limit = limit; + this.func = func; + this.key = key; + this.store = store || new DefaultStore(); + } -Cache.prototype = { - set(key, value) { + set(obj, value) { if (this.limit > this.size) { + let key = this.key === undefined ? obj : this.key(obj); this.size ++; if (value === undefined) { - this.store[key] = UNDEFINED; + this.store.set(key, UNDEFINED); } else { - this.store[key] = value; + this.store.set(key, value); } } - return value; - }, - - get(key) { - let value = this.store[key]; + } + get(obj) { + let key = this.key === undefined ? obj : this.key(obj); + let value = this.store.get(key); if (value === undefined) { this.misses ++; - value = this.set(key, this.func(key)); + value = this.set(key, this.func(obj)); } else if (value === UNDEFINED) { this.hits ++; value = undefined; @@ -41,12 +39,32 @@ Cache.prototype = { } return value; - }, + } purge() { - this.store = new EmptyObject(); + this.store.clear(); this.size = 0; this.hits = 0; this.misses = 0; } -}; +} + +function UNDEFINED() {} + +class DefaultStore { + constructor() { + this.data = new EmptyObject(); + } + + get(key) { + return this.data[key]; + } + + set(key, value) { + this.data[key] = value; + } + + clear() { + this.data = new EmptyObject(); + } +}