optimization and refactoring, rules are first flattened and then

processed, letting us to set individual rules as "lazy"
This commit is contained in:
Henry Jameson 2024-02-26 21:37:40 +02:00
commit dc22386599
3 changed files with 243 additions and 250 deletions

View file

@ -151,6 +151,7 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
const eagerRules = []
const lazyRules = []
const lazyPromises = []
const rulesetUnsorted = [
...Object.values(components)
@ -187,25 +188,210 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
const virtualComponents = new Set(Object.values(components).filter(c => c.virtual).map(c => c.name))
let counter = 0
const promises = []
const processInnerComponent = (component, rules, parent) => {
const processCombination = (combination, rules) => {
const addRule = (rule) => {
rules.push(rule)
}
const parentSelector = ruleToSelector(parent, true)
// const parentList = parent ? unroll(parent).reverse().map(c => c.component) : []
// if (!component.virtual) {
// const path = [...parentList, component.name].join(' > ')
// console.log('Component ' + path + ' process starting')
// }
// const t0 = performance.now()
const selector = ruleToSelector(combination, true)
const cssSelector = ruleToSelector(combination)
const parentSelector = selector.split(/ /g).slice(0, -1).join(' ')
const soloSelector = selector.split(/ /g).slice(-1)[0]
const lowerLevelSelector = parentSelector
const lowerLevelBackground = computed[lowerLevelSelector]?.background
const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives
const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw
const dynamicVars = computed[selector] || {
lowerLevelBackground,
lowerLevelVirtualDirectives,
lowerLevelVirtualDirectivesRaw
}
// Inheriting all of the applicable rules
const existingRules = ruleset.filter(findRules(combination))
const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {})
const computedRule = {
...combination,
directives: computedDirectives
}
computed[selector] = computed[selector] || {}
computed[selector].computedRule = computedRule
computed[selector].dynamicVars = dynamicVars
if (virtualComponents.has(combination.component)) {
const virtualName = [
'--',
combination.component.toLowerCase(),
combination.variant === 'normal'
? ''
: combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
].join('')
let inheritedTextColor = computedDirectives.textColor
let inheritedTextAuto = computedDirectives.textAuto
let inheritedTextOpacity = computedDirectives.textOpacity
let inheritedTextOpacityMode = computedDirectives.textOpacityMode
const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
const lowerLevelTextRule = computed[lowerLevelTextSelector]
if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto
inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
}
const newTextRule = {
...computedRule,
directives: {
...computedRule.directives,
textColor: inheritedTextColor,
textAuto: inheritedTextAuto ?? 'preserve',
textOpacity: inheritedTextOpacity,
textOpacityMode: inheritedTextOpacityMode
}
}
dynamicVars.inheritedBackground = lowerLevelBackground
dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb
const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb
const textColor = newTextRule.directives.textAuto === 'no-auto'
? intendedTextColor
: getTextColor(
convert(stacked[lowerLevelSelector]).rgb,
intendedTextColor,
newTextRule.directives.textAuto === 'preserve'
)
// Updating previously added rule
const earlyLowerLevelRules = rules.filter(findRules(combination.parent, true))
const earlyLowerLevelRule = earlyLowerLevelRules.slice(-1)[0]
const virtualDirectives = earlyLowerLevelRule.virtualDirectives || {}
const virtualDirectivesRaw = earlyLowerLevelRule.virtualDirectivesRaw || {}
// Storing color data in lower layer to use as custom css properties
virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
virtualDirectivesRaw[virtualName] = textColor
earlyLowerLevelRule.virtualDirectives = virtualDirectives
earlyLowerLevelRule.virtualDirectivesRaw = virtualDirectivesRaw
computed[lowerLevelSelector].virtualDirectives = virtualDirectives
computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw
} else {
computed[selector] = computed[selector] || {}
// TODO: DEFAULT TEXT COLOR
const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb
if (computedDirectives.background) {
let inheritRule = null
const variantRules = ruleset.filter(
findRules({
component: combination.component,
variant: combination.variant,
parent: combination.parent
})
)
const lastVariantRule = variantRules[variantRules.length - 1]
if (lastVariantRule) {
inheritRule = lastVariantRule
} else {
const normalRules = ruleset.filter(findRules({
component: combination.component,
parent: combination.parent
}))
const lastNormalRule = normalRules[normalRules.length - 1]
inheritRule = lastNormalRule
}
const inheritSelector = ruleToSelector({ ...inheritRule, parent: combination.parent }, true)
const inheritedBackground = computed[inheritSelector].background
dynamicVars.inheritedBackground = inheritedBackground
const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb
if (!stacked[selector]) {
let blend
const alpha = computedDirectives.opacity ?? 1
if (alpha >= 1) {
blend = rgb
} else if (alpha <= 0) {
blend = lowerLevelStackedBackground
} else {
blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground)
}
stacked[selector] = blend
computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 }
}
}
if (computedDirectives.shadow) {
dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars }))
}
if (!stacked[selector]) {
computedDirectives.background = 'transparent'
computedDirectives.opacity = 0
stacked[selector] = lowerLevelStackedBackground
computed[selector].background = { ...lowerLevelStackedBackground, a: 0 }
}
dynamicVars.stacked = stacked[selector]
dynamicVars.background = computed[selector].background
const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--'))
dynamicSlots.forEach(([k, v]) => {
const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme!
switch (type) {
case 'color': {
const color = findColor(value[0], { dynamicVars, staticVars })
dynamicVars[k] = color
if (combination.component === 'Root') {
staticVars[k.substring(2)] = color
}
break
}
case 'shadow': {
const shadow = value
dynamicVars[k] = shadow
if (combination.component === 'Root') {
staticVars[k.substring(2)] = shadow
}
break
}
case 'generic': {
dynamicVars[k] = value
if (combination.component === 'Root') {
staticVars[k.substring(2)] = value
}
break
}
}
})
addRule({
dynamicVars,
selector: cssSelector,
...combination,
directives: computedDirectives
})
}
}
const processInnerComponent = (component, parent) => {
const combinations = []
const {
validInnerComponents = [],
states: originalStates = {},
variants: originalVariants = {},
name
variants: originalVariants = {}
} = component
// Normalizing states and variants to always include "normal"
@ -241,233 +427,43 @@ export const init = (extraRuleset, ultimateBackgroundColor) => {
}).reduce((acc, x) => [...acc, ...x], [])
stateVariantCombination.forEach(combination => {
counter++
// const tt0 = performance.now()
combination.component = component.name
const soloSelector = ruleToSelector(combination, true)
const soloCssSelector = ruleToSelector(combination)
const selector = [parentSelector, soloSelector].filter(x => x).join(' ')
const cssSelector = [parentSelector, soloCssSelector].filter(x => x).join(' ')
const lowerLevelSelector = parentSelector
const lowerLevelBackground = computed[lowerLevelSelector]?.background
const lowerLevelVirtualDirectives = computed[lowerLevelSelector]?.virtualDirectives
const lowerLevelVirtualDirectivesRaw = computed[lowerLevelSelector]?.virtualDirectivesRaw
const dynamicVars = computed[selector] || {
lowerLevelBackground,
lowerLevelVirtualDirectives,
lowerLevelVirtualDirectivesRaw
combination.lazy = component.lazy || parent?.lazy
combination.parent = parent
if (combination.state.indexOf('hover') >= 0) {
combination.lazy = true
}
// Inheriting all of the applicable rules
const existingRules = ruleset.filter(findRules({ component: component.name, ...combination, parent }))
const computedDirectives = existingRules.map(r => r.directives).reduce((acc, directives) => ({ ...acc, ...directives }), {})
const computedRule = {
component: component.name,
...combination,
parent,
directives: computedDirectives
}
computed[selector] = computed[selector] || {}
computed[selector].computedRule = computedRule
computed[selector].dynamicVars = dynamicVars
if (virtualComponents.has(component.name)) {
const virtualName = [
'--',
component.name.toLowerCase(),
combination.variant === 'normal'
? ''
: combination.variant[0].toUpperCase() + combination.variant.slice(1).toLowerCase(),
...combination.state.filter(x => x !== 'normal').toSorted().map(state => state[0].toUpperCase() + state.slice(1).toLowerCase())
].join('')
let inheritedTextColor = computedDirectives.textColor
let inheritedTextAuto = computedDirectives.textAuto
let inheritedTextOpacity = computedDirectives.textOpacity
let inheritedTextOpacityMode = computedDirectives.textOpacityMode
const lowerLevelTextSelector = [...selector.split(/ /g).slice(0, -1), soloSelector].join(' ')
const lowerLevelTextRule = computed[lowerLevelTextSelector]
if (inheritedTextColor == null || inheritedTextOpacity == null || inheritedTextOpacityMode == null) {
inheritedTextColor = computedDirectives.textColor ?? lowerLevelTextRule.textColor
inheritedTextAuto = computedDirectives.textAuto ?? lowerLevelTextRule.textAuto
inheritedTextOpacity = computedDirectives.textOpacity ?? lowerLevelTextRule.textOpacity
inheritedTextOpacityMode = computedDirectives.textOpacityMode ?? lowerLevelTextRule.textOpacityMode
}
const newTextRule = {
...computedRule,
directives: {
...computedRule.directives,
textColor: inheritedTextColor,
textAuto: inheritedTextAuto ?? 'preserve',
textOpacity: inheritedTextOpacity,
textOpacityMode: inheritedTextOpacityMode
}
}
dynamicVars.inheritedBackground = lowerLevelBackground
dynamicVars.stacked = convert(stacked[lowerLevelSelector]).rgb
const intendedTextColor = convert(findColor(inheritedTextColor, { dynamicVars, staticVars })).rgb
const textColor = newTextRule.directives.textAuto === 'no-auto'
? intendedTextColor
: getTextColor(
convert(stacked[lowerLevelSelector]).rgb,
intendedTextColor,
newTextRule.directives.textAuto === 'preserve'
)
// Updating previously added rule
const earlyLowerLevelRules = rules.filter(findRules(parent, true))
const earlyLowerLevelRule = earlyLowerLevelRules.slice(-1)[0]
const virtualDirectives = earlyLowerLevelRule.virtualDirectives || {}
const virtualDirectivesRaw = earlyLowerLevelRule.virtualDirectivesRaw || {}
// Storing color data in lower layer to use as custom css properties
virtualDirectives[virtualName] = getTextColorAlpha(newTextRule.directives, textColor, dynamicVars)
virtualDirectivesRaw[virtualName] = textColor
earlyLowerLevelRule.virtualDirectives = virtualDirectives
earlyLowerLevelRule.virtualDirectivesRaw = virtualDirectivesRaw
computed[lowerLevelSelector].virtualDirectives = virtualDirectives
computed[lowerLevelSelector].virtualDirectivesRaw = virtualDirectivesRaw
} else {
computed[selector] = computed[selector] || {}
// TODO: DEFAULT TEXT COLOR
const lowerLevelStackedBackground = stacked[lowerLevelSelector] || convert(ultimateBackgroundColor).rgb
if (computedDirectives.background) {
let inheritRule = null
const variantRules = ruleset.filter(findRules({ component: component.name, variant: combination.variant, parent }))
const lastVariantRule = variantRules[variantRules.length - 1]
if (lastVariantRule) {
inheritRule = lastVariantRule
} else {
const normalRules = ruleset.filter(findRules({ component: component.name, parent }))
const lastNormalRule = normalRules[normalRules.length - 1]
inheritRule = lastNormalRule
}
const inheritSelector = ruleToSelector({ ...inheritRule, parent }, true)
const inheritedBackground = computed[inheritSelector].background
dynamicVars.inheritedBackground = inheritedBackground
const rgb = convert(findColor(computedDirectives.background, { dynamicVars, staticVars })).rgb
if (!stacked[selector]) {
let blend
const alpha = computedDirectives.opacity ?? 1
if (alpha >= 1) {
blend = rgb
} else if (alpha <= 0) {
blend = lowerLevelStackedBackground
} else {
blend = alphaBlend(rgb, computedDirectives.opacity, lowerLevelStackedBackground)
}
stacked[selector] = blend
computed[selector].background = { ...rgb, a: computedDirectives.opacity ?? 1 }
}
}
if (computedDirectives.shadow) {
dynamicVars.shadow = flattenDeep(findShadow(flattenDeep(computedDirectives.shadow), { dynamicVars, staticVars }))
}
if (!stacked[selector]) {
computedDirectives.background = 'transparent'
computedDirectives.opacity = 0
stacked[selector] = lowerLevelStackedBackground
computed[selector].background = { ...lowerLevelStackedBackground, a: 0 }
}
dynamicVars.stacked = stacked[selector]
dynamicVars.background = computed[selector].background
const dynamicSlots = Object.entries(computedDirectives).filter(([k, v]) => k.startsWith('--'))
dynamicSlots.forEach(([k, v]) => {
const [type, ...value] = v.split('|').map(x => x.trim()) // woah, Extreme!
switch (type) {
case 'color': {
const color = findColor(value[0], { dynamicVars, staticVars })
dynamicVars[k] = color
if (component.name === 'Root') {
staticVars[k.substring(2)] = color
}
break
}
case 'shadow': {
const shadow = value
dynamicVars[k] = shadow
if (component.name === 'Root') {
staticVars[k.substring(2)] = shadow
}
break
}
case 'generic': {
dynamicVars[k] = value
if (component.name === 'Root') {
staticVars[k.substring(2)] = value
}
break
}
}
})
addRule({
dynamicVars,
selector: cssSelector,
component: component.name,
...combination,
parent,
directives: computedDirectives
})
}
combinations.push(combination)
innerComponents.forEach(innerComponent => {
if (innerComponent.lazy) {
promises.push(new Promise((resolve, reject) => {
setTimeout(() => {
try {
processInnerComponent(innerComponent, lazyRules, { parent, component: name, ...combination })
resolve()
} catch (e) {
reject(e)
}
}, 0)
}))
} else {
processInnerComponent(innerComponent, rules, { parent, component: name, ...combination })
}
combinations.push(...processInnerComponent(innerComponent, combination))
})
// const tt1 = performance.now()
// if (!component.virtual) {
// console.log('State-variant ' + combination.variant + ' : ' + combination.state.join('+') + ' procession time: ' + (tt1 - tt0) + 'ms')
// }
})
// const t1 = performance.now()
// if (!component.virtual) {
// const path = [...parentList, component.name].join(' > ')
// console.log('Component ' + path + ' procession time: ' + (t1 - t0) + 'ms')
// }
return combinations
}
processInnerComponent(components.Root, eagerRules)
console.debug('Eager combinations processed:' + counter)
const lazyExec = Promise.all(promises).then(() => {
console.debug('Total combinations processed: ' + counter)
}).then(() => lazyRules)
const t0 = performance.now()
const combinations = processInnerComponent(components.Root)
const t1 = performance.now()
console.debug('Tree tranveral took ' + (t1 - t0) + ' ms')
combinations.forEach((combination) => {
if (combination.lazy) {
lazyPromises.push(async () => processCombination(combination, lazyRules))
} else {
processCombination(combination, eagerRules)
}
})
const t2 = performance.now()
console.debug('Eager processing took ' + (t2 - t1) + ' ms')
return {
lazy: lazyExec,
lazy: async () => {
await Promise.all(lazyPromises.map(x => x()))
return lazyRules
},
eager: eagerRules,
staticVars
}