Skip to content

Use tries to make everything fast #681

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 11 commits into
base: master
Choose a base branch
from
Open
32 changes: 32 additions & 0 deletions haxe/ui/backend/ComponentBase.hx
Original file line number Diff line number Diff line change
Expand Up @@ -1378,6 +1378,10 @@ class ComponentBase extends ComponentSurface implements IClonable<ComponentBase>
return; //it should be added into the queue later
}

if (isComponentValidationPaused) {
return;
}

var isAlreadyInvalid:Bool = isComponentInvalid(flag);
var isAlreadyDelayedInvalid:Bool = false;
if (_isValidating == true) {
Expand Down Expand Up @@ -1568,7 +1572,35 @@ class ComponentBase extends ComponentSurface implements IClonable<ComponentBase>
}
}

private var _validationPaused:Bool = false;
public function pauseComponentValidation() {
_validationPaused = true;
}

private var isComponentValidationPaused(get, never):Bool;
private function get_isComponentValidationPaused():Bool {
var ref = this;
while (ref != null) {
if (ref._validationPaused) {
return true;
}
ref = ref.parentComponent;
}
return false;
}

public function resumeComponentValidation() {
Toolkit.callLater(() -> {
_validationPaused = false;
invalidateComponent(true);
});
}

private function validateComponentInternal(nextFrame:Bool = true) {
if (isComponentValidationPaused) {
return;
}

var dataInvalid = isComponentInvalid(InvalidationFlags.DATA);
var styleInvalid = isComponentInvalid(InvalidationFlags.STYLE);
var textDisplayInvalid = isComponentInvalid(InvalidationFlags.TEXT_DISPLAY) && hasTextDisplay();
Expand Down
7 changes: 5 additions & 2 deletions haxe/ui/containers/Collapsible.hx
Original file line number Diff line number Diff line change
Expand Up @@ -151,8 +151,6 @@ class CollapsibleBuilder extends CompositeBuilder {
super(collapsible);
_collapsible = collapsible;
// we'll start off as non-animatable so things dont animate at the start of the component creation
_originalAnimatable = _collapsible.animatable;
_collapsible.animatable = false;
_component.recursivePointerEvents = false;
_header = new HBox();
_header.percentWidth = 100;
Expand Down Expand Up @@ -182,6 +180,11 @@ class CollapsibleBuilder extends CompositeBuilder {
_collapsible.registerInternalEvents(true);
}

public override function onInitialize() {
_originalAnimatable = _collapsible.animatable;
_collapsible.animatable = false;
}

public override function onReady() {
super.onReady();
_collapsible.animatable = _originalAnimatable;
Expand Down
22 changes: 22 additions & 0 deletions haxe/ui/core/Component.hx
Original file line number Diff line number Diff line change
Expand Up @@ -1263,6 +1263,7 @@ class Component extends ComponentImpl
private function set_customStyle(value:Style):Style {
if (value != _customStyle) {
invalidateComponentStyle();
_updateStyleCacheKey();
}
_customStyle = value;
return value;
Expand All @@ -1285,6 +1286,7 @@ class Component extends ComponentImpl
classes.push(name);
if (invalidate == true) {
invalidateComponentStyle();
_updateStyleCacheKey();
}
}

Expand Down Expand Up @@ -1316,6 +1318,7 @@ class Component extends ComponentImpl

if (needsInvalidate == true) {
invalidateComponentStyle();
_updateStyleCacheKey();
}

if (recursive == true) {
Expand All @@ -1338,6 +1341,7 @@ class Component extends ComponentImpl
classes.remove(name);
if (invalidate == true) {
invalidateComponentStyle();
_updateStyleCacheKey();
}
}

Expand Down Expand Up @@ -1369,6 +1373,7 @@ class Component extends ComponentImpl

if (needsInvalidate == true) {
invalidateComponentStyle();
_updateStyleCacheKey();
}

if (recursive == true) {
Expand Down Expand Up @@ -1411,6 +1416,7 @@ class Component extends ComponentImpl

if (invalidate == true && needsInvalidate == true) {
invalidateComponentStyle();
_updateStyleCacheKey();
}

if (recursive == true) {
Expand Down Expand Up @@ -1442,6 +1448,7 @@ class Component extends ComponentImpl

if (invalidate == true && needsInvalidate == true) {
invalidateComponentStyle();
_updateStyleCacheKey();
}

if (recursive == true) {
Expand Down Expand Up @@ -1552,9 +1559,23 @@ class Component extends ComponentImpl

_styleString = value;
invalidateComponentStyle();
_updateStyleCacheKey();
return value;
}

private var _styleCacheKey:Array<String> = [];
function _updateStyleCacheKey() {
_styleCacheKey = [];
var ref = this;
while (ref != null) {
_styleCacheKey.push(ref.className);
// _styleCacheKey.push(ref.id);
_styleCacheKey = _styleCacheKey.concat(ref.classes);
ref = ref.parentComponent;
}
_styleCacheKey.reverse();
}

// were going to cache the ref (which may be null) so we dont have to
// perform a parent based lookup each for a performance tweak
@:noCompletion private var _useCachedStyleSheetRef:Bool = false;
Expand Down Expand Up @@ -1708,6 +1729,7 @@ class Component extends ComponentImpl
Toolkit.callLater(function() {
invalidateComponentData();
invalidateComponentStyle();
_updateStyleCacheKey();

if (_compositeBuilder != null) {
_compositeBuilder.onReady();
Expand Down
57 changes: 53 additions & 4 deletions haxe/ui/styles/StyleSheet.hx
Original file line number Diff line number Diff line change
Expand Up @@ -6,12 +6,24 @@ import haxe.ui.styles.elements.AnimationKeyFrames;
import haxe.ui.styles.elements.ImportElement;
import haxe.ui.styles.elements.MediaQuery;
import haxe.ui.styles.elements.RuleElement;
import haxe.ui.styles.elements.Directive;
#if new_selectors
import haxe.ui.styles.selector.SelectorData;
import haxe.ui.styles.selector.SelectorSpecificity;
#end

using StringTools;

class StyleSheet {
public var name:String;

private var _styleCacheTree = new haxe.utils.trie.Trie<String, Array<Map<String, Directive>>>();

#if new_selectors
// private var _directives = new Map<String, Directive>();
// private var _selectedSelectors = new Map<String, SelectorVO>();
#end

private var _imports:Array<ImportElement> = [];
private var _rules:Array<RuleElement> = [];

Expand Down Expand Up @@ -146,12 +158,49 @@ class StyleSheet {
if (style == null) {
style = {};
}
for (r in rules) {
if (!r.match(c)) {
continue;

final key = @:privateAccess c._styleCacheKey;
var cachedDirectives = _styleCacheTree.get(key);
if (cachedDirectives != null) {
for (d in cachedDirectives)
style.mergeDirectives(d);
} else
{

cachedDirectives = [];

#if new_selectors
// _directives.clear();
// _selectedSelectors.clear();
#end

for (r in rules) {
if (!r.match(c)) {
continue;
}
#if new_selectors
// this was some code for specifity but this is broken with the caching now. ignore it
// for (k=>v in r.directives) {
// if (!_directives.exists(k)) {
// _directives.set(k, v);
// _selectedSelectors.set(k, r.selector);
// } else {
// if (SelectorSpecificity.get(r.selector) >= SelectorSpecificity.get(_selectedSelectors.get(k))) {
// _directives.set(k, v);
// _selectedSelectors.set(k, r.selector);
// }
// }
// }
// cachedDirectives.push(_directives);
cachedDirectives.push(r.directives);
#else
cachedDirectives.push(r.directives);
#end
}

style.mergeDirectives(r.directives);
for (d in cachedDirectives)
style.mergeDirectives(d);
_styleCacheTree.set(key, cachedDirectives);
}

return style;
Expand Down
131 changes: 130 additions & 1 deletion haxe/ui/styles/elements/RuleElement.hx
Original file line number Diff line number Diff line change
Expand Up @@ -2,15 +2,29 @@ package haxe.ui.styles.elements;

import haxe.ui.core.Component;
import haxe.ui.styles.Value;
#if new_selectors
import haxe.ui.styles.selector.SelectorMatcher;
import haxe.ui.styles.selector.SelectorParser;
import haxe.ui.styles.selector.SelectorData;
#end

@:access(haxe.ui.core.Component)
class RuleElement {
#if new_selectors
public var selector:SelectorVO;
static var matchedPseudoClasses = new MatchedPseudoClassesVO(false, false, false, false, false, false, false, false, false, false, null, null, null);
#else
public var selector:Selector;
#end
public var directives:Map<String, Directive> = new Map<String, Directive>();
public var directiveCount:Int = 0;

public function new(selector:String, directives:Array<Directive>) {
#if new_selectors
this.selector = SelectorParser.parse(selector);
#else
this.selector = new Selector(selector);
#end
//this.directives = directives;

for (d in directives) {
Expand All @@ -25,7 +39,122 @@ class RuleElement {
}

public function match(d:Component):Bool {

#if new_selectors
matchedPseudoClasses.hover = false;
matchedPseudoClasses.focus = false;
matchedPseudoClasses.active = false;
matchedPseudoClasses.link = false;
matchedPseudoClasses.enabled = false;
matchedPseudoClasses.disabled = false;
matchedPseudoClasses.checked = false;
matchedPseudoClasses.fullscreen = false;

matchedPseudoClasses.hasClasses = d.classes.length > 0;
matchedPseudoClasses.nodeClassList = d.classes;

if (matchedPseudoClasses.hasClasses) {
for (c in (d.classes:Array<String>)) {
if (c == ':hover') matchedPseudoClasses.hover = true;
else if (c == ':focus') matchedPseudoClasses.focus = true;
else if (c == ':active') matchedPseudoClasses.active = true;
else if (c == ':link') matchedPseudoClasses.link = true;
else if (c == ':enabled') matchedPseudoClasses.enabled = true;
else if (c == ':disabled') matchedPseudoClasses.disabled = true;
else if (c == ':checked') matchedPseudoClasses.checked = true;
else if (c == ':fullscreen') matchedPseudoClasses.fullscreen = true;
}
}

matchedPseudoClasses.hasId = true;
matchedPseudoClasses.nodeId = d.id;
matchedPseudoClasses.nodeType = d.className;

// naive version, full match every rule:
// final res = SelectorMatcher.match(d, selector, matchedPseudoClasses);
// trace('$res: ${selector.toString()} == <${@:privateAccess d.className} id=${d.id} class=${d.classes}>');
// return res;

var match:Bool = false;

//to optimise speed the matchSelector method must be called
//the least time possible

//if the selector begins with a class,
//then only match if the node has at least one class,
//and contains the first class of the selector
if (selector.beginsWithClass) {
if (matchedPseudoClasses.hasClasses) {
var classListLength:Int = matchedPseudoClasses.nodeClassList.length;
for (cls in matchedPseudoClasses.nodeClassList) {
if (cls == selector.firstClass) {
// in this case, the selector only has a single
// class selector, so it is a match
if (selector.isSimpleClassSelector == true) {
match = true;
break;
}
//else need to perform a full match
else {
match = SelectorMatcher.match(d, selector, matchedPseudoClasses) == true;
break;
}
}
}
}
}
//if the selector begins with an id selector, only match node if
//it has an id
else if (selector.beginsWithId == true) {
if (matchedPseudoClasses.hasId == true) {
if (matchedPseudoClasses.nodeId == selector.firstId) {
//if the selector consists of only an Id, it is a match
if (selector.isSimpleIdSelector == true)
match = true;
//else need to perform a full match
else
match = SelectorMatcher.match(d, selector, matchedPseudoClasses) == true;
}
}
}
//if the selector begins with a type, only match node wih the
//same type
else if (selector.beginsWithType == true) {
if (matchedPseudoClasses.nodeType == selector.firstType) {
//if the selector is only a type selector, then it matches
if (selector.isSimpleTypeSelector == true)
match = true;
//else a full match is needed
else
match = SelectorMatcher.match(d, selector, matchedPseudoClasses) == true;
}
}
//in other cases, full match
else
match = SelectorMatcher.match(d, selector, matchedPseudoClasses) == true;

// if (match == true)
// {
// //if the selector is matched, store the coresponding style declaration
// //along with the matching selector
// var matchingStyleDeclaration:StyleDeclarationVO = new StyleDeclarationVO();
// matchingStyleDeclaration.style = styleRule.style;
// matchingStyleDeclaration.selector = selectors[k];
// _matchingStyleDeclaration.push(matchingStyleDeclaration);

// //break to prevent from adding a style declaration
// //multiplt time if more than one selector
// //matches
// break;
// }

// if (matchedPseudoClasses.hover)
// trace('$match: ${selector.toString()} == <${@:privateAccess d.tagName} id=${d.id} class=${d.classList}> {$directives}');

return match;
#else
return ruleMatch(selector.parts[selector.parts.length - 1], d);
#end
}

private static function ruleMatch( c : SelectorPart, d : Component ):Bool {
Expand Down Expand Up @@ -212,4 +341,4 @@ class RuleElement {
trace("unknown value type", d.value);
}
}
}
}
Loading