1
0
Fork 0

all part resolution business happens on a per-part basis

This commit is contained in:
Enoch Riese 2023-02-20 23:59:28 +02:00
parent e1990bbb99
commit 34fc6953e2
2 changed files with 91 additions and 161 deletions

View file

@ -13,8 +13,7 @@ import { version } from '../data.mjs'
import { __loadPatternDefaults } from './config.mjs'
import cloneDeep from 'lodash.clonedeep'
const DISTANCE_DEBUG = true
const DISTANCE_DEBUG = false
//////////////////////////////////////////////
// CONSTRUCTOR //
//////////////////////////////////////////////
@ -74,12 +73,11 @@ export function Pattern(designConfig) {
* @param {object} part - The part to add
* @return {object} this - The Pattern instance
*/
Pattern.prototype.addPart = function (part, runtime = false) {
Pattern.prototype.addPart = function (part) {
if (typeof part?.draft === 'function') {
if (part.name) {
this.designConfig.parts.push(part)
if (runtime) {
} else this.__initialized = false
this.__initialized = false
} else this.store.log.error(`Part must have a name`)
} else this.store.log.error(`Part must have a draft() method`)
@ -369,14 +367,12 @@ Pattern.prototype.use = function (plugin, data) {
* @param {object} dep - The dependency configuration
* @return {object} this - The Pattern instance
*/
Pattern.prototype.__addDependency = function (name, part, dep) {
this.__dependencies[name] = mergeDependencies(dep.name, this.__dependencies[name])
// #FIXME What's supposed to happen here?
if (typeof this.__designParts[dep.name] === 'undefined') {
this.config = this.__addPartConfig(this.__designParts[dep.name])
}
return this
Pattern.prototype.__addDependency = function (dependencyList, part, dep) {
this[dependencyList][part.name] = this[dependencyList][part.name] || []
if (dependencyList == '__resolvedDependencies' && DISTANCE_DEBUG)
this.store.log.debug(`add ${dep.name} to ${part.name} dependencyResolution`)
if (this[dependencyList][part.name].indexOf(dep.name) === -1)
this[dependencyList][part.name].push(dep.name)
}
/**
@ -476,7 +472,7 @@ Pattern.prototype.__addPartOptions = function (part) {
} else {
if (DISTANCE_DEBUG)
this.store.log.debug(
`optionDistance for ${optionName} is ${optionDistance} and partDistance for ${part.name} is ${partDistance}`
`optionDistance for __${optionName}__ is __${optionDistance}__ and partDistance for \`${part.name}\` is __${partDistance}__`
)
if (optionDistance > partDistance) {
this.config.options[optionName] = part.options[optionName]
@ -552,6 +548,11 @@ Pattern.prototype.__addPartPlugins = function (part) {
`Plugin \`${name}\` was requested conditionally, but is already added explicitly. Not loading.`
)
}
// swap from a conditional if needed
else if (plugins[name].condition) {
plugins[name] = plugin
this.store.log.info(`Plugin \`${name}\` was explicitly added. Changing from conditional.`)
}
}
}
@ -741,21 +742,11 @@ Pattern.prototype.__isPartHidden = function (partName) {
Pattern.prototype.__isStackHidden = function (stackName) {
if (!this.stacks[stackName]) return true
const parts = this.stacks[stackName].getPartNames()
if (Array.isArray(this.settings[this.activeStack || 0].only)) {
for (const partName of parts) {
if (this.settings[this.activeStack || 0].only.includes(partName)) return false
}
return true
}
for (const partName of parts) {
if (this.__designParts?.[partName]?.hide) return true
if (this.__designParts?.[partName]?.hideAll) return true
if (this.__mutated.partHide?.[partName]) return true
if (this.__mutated.partHideAll?.[partName]) return true
if (this.parts?.[this.activeSet]?.[partName]?.hidden) return true
if (!this.__isPartHidden(partName)) return false
}
return false
return true
}
/**
@ -1189,35 +1180,6 @@ Pattern.prototype.__pack = function () {
return this
}
/**
* Recursively solves part dependencies for a part
*
* @private
* @param {object} seen - Object to keep track of seen dependencies
* @param {string} part - Name of the part
* @param {object} graph - Dependency graph, used to call itself recursively
* @param {array} deps - List of dependencies
* @return {Array} deps - The list of dependencies
*/
Pattern.prototype.__resolveDependency = function (
seen,
part,
graph = this.dependencies,
deps = []
) {
if (typeof seen[part] === 'undefined') seen[part] = true
if (typeof graph[part] === 'string') graph[part] = [graph[part]]
if (Array.isArray(graph[part])) {
if (graph[part].length === 0) return []
else {
if (deps.indexOf(graph[part]) === -1) deps.push(...graph[part])
for (let apart of graph[part]) deps.concat(this.__resolveDependency(seen, apart, graph, deps))
}
}
return deps
}
/**
* Resolves the draft order based on the configuation
*
@ -1226,37 +1188,23 @@ Pattern.prototype.__resolveDependency = function (
* @return {Pattern} this - The Pattern instance
*/
Pattern.prototype.__resolveDraftOrder = function (graph = this.__resolvedDependencies) {
let sorted = []
let visited = {}
Object.keys(graph).forEach(function visit(name, ancestors) {
if (!Array.isArray(ancestors)) ancestors = []
ancestors.push(name)
visited[name] = true
if (typeof graph[name] !== 'undefined') {
graph[name].forEach(function (dep) {
if (visited[dep]) return
visit(dep, ancestors.slice(0))
})
}
if (sorted.indexOf(name) < 0) sorted.push(name)
})
// Don't forget about parts without dependencies
for (const part in this.__designParts) {
if (sorted.indexOf(part) === -1) sorted.push(part)
}
const sorted = Object.keys(this.__designParts).sort(
(p1, p2) => this.__mutated.partDistance[p2] - this.__mutated.partDistance[p1]
)
this.__draftOrder = sorted
this.config.draftOrder = sorted
return this
}
Pattern.prototype.__resolvePartMutation = function (part, dependency, depType) {
const current_part_distance = this.__mutated.partDistance[part.name]
const proposed_dependent_part_distance = current_part_distance + 1
Pattern.prototype.__resolvePartDependencyChain = function (depChain, dependency, depType) {
const part = depChain[0]
this.__designParts[dependency.name] = Object.freeze(dependency)
this.__addDependency('__dependencies', part, dependency)
depChain.forEach((c) => this.__addDependency('__resolvedDependencies', c, dependency))
switch (depType) {
case 'from':
this.__setFromHide(part, part.name, dependency.name)
@ -1264,35 +1212,72 @@ Pattern.prototype.__resolvePartMutation = function (part, dependency, depType) {
break
case 'after':
this.__setAfterHide(part, part.name, dependency.name)
this.__addDependency(part.name, part, dependency)
}
if (
typeof this.__mutated.partDistance[dependency.name] === 'undefined' ||
this.__mutated.partDistance[dependency.name] < proposed_dependent_part_distance
) {
this.__mutated.partDistance[dependency.name] = proposed_dependent_part_distance
if (DISTANCE_DEBUG)
this.store.log.debug(
`"${depType}:" partDistance for ${dependency.name} is ${
this.__mutated.partDistance[dependency.name]
}`
)
}
}
Pattern.prototype.__resolvePart = function (part, distance = 0) {
Pattern.prototype.__resolveMutatedPartDistance = function (partName) {
const proposed_dependent_part_distance = this.__mutated.partDistance[partName] + 1
let didChange = false
if (!this.__dependencies[partName]) return false
this.__dependencies[partName].forEach((dependency) => {
if (
typeof this.__mutated.partDistance[dependency] === 'undefined' ||
this.__mutated.partDistance[dependency] < proposed_dependent_part_distance
) {
didChange = true
this.__mutated.partDistance[dependency] = proposed_dependent_part_distance
this.__resolveMutatedPartDistance(dependency)
}
if (DISTANCE_DEBUG)
this.store.log.debug(
`"${depType}:" partDistance for \`${dependency}\` is __${this.__mutated.partDistance[dependency]}__`
)
})
return didChange
}
const depTypes = ['from', 'after']
Pattern.prototype.__resolvePartDependencies = function (depChain, distance) {
// Resolve part Dependencies. first from then after
const part = depChain[0]
this.__resolvedDependencies[part.name] = this.__resolvedDependencies[part.name] || []
depTypes.forEach((d) => {
if (part[d]) {
if (DISTANCE_DEBUG) this.store.log.debug(`Processing \`${part.name}\` "${d}:"`)
const depsOfType = Array.isArray(part[d]) ? part[d] : [part[d]]
depsOfType.forEach((dot) => {
let count = Object.keys(this.__designParts).length
// if any changes resulted from resolving this part mutation
this.__resolvePartDependencyChain(depChain, dot, d)
// if a new part was added, resolve the part
const newCount = Object.keys(this.__designParts).length
if (count < newCount) {
this.__resolvePart([dot, ...depChain], distance)
count = newCount
}
})
}
})
this.__resolveMutatedPartDistance(part.name)
}
Pattern.prototype.__resolvePart = function (depChain, distance = 0) {
const part = depChain[0]
if (distance === 0) {
this.__designParts[part.name] = Object.freeze(part)
}
let count = Object.keys(this.__designParts).length
distance++
if (typeof this.__mutated.partDistance[part.name] === 'undefined') {
this.__mutated.partDistance[part.name] = distance
if (DISTANCE_DEBUG)
this.store.log.debug(
`Base partDistance for ${part.name} is ${this.__mutated.partDistance[part.name]}`
`Base partDistance for \`${part.name}\` is __${this.__mutated.partDistance[part.name]}__`
)
}
@ -1301,23 +1286,7 @@ Pattern.prototype.__resolvePart = function (part, distance = 0) {
this.__mutated.partHide[part.name] = true
}
// Resolve part mutations. first from then after
;['from', 'after'].forEach((d) => {
if (part[d]) {
if (DISTANCE_DEBUG) this.store.log.debug(`Processing ${part.name} "${d}:"`)
const depsOfType = Array.isArray(part[d]) ? part[d] : [part[d]]
depsOfType.forEach((dot) => {
this.__resolvePartMutation(part, dot, d)
const newCount = Object.keys(this.__designParts).length
if (count < newCount) {
this.__resolvePart(dot, distance)
count = newCount
}
})
}
})
this.__resolvePartDependencies(depChain, distance)
// add the part's config
this.__addPartConfig(part)
@ -1332,21 +1301,19 @@ Pattern.prototype.__resolvePart = function (part, distance = 0) {
*/
Pattern.prototype.__resolveParts = function (count = 0, distance = 0) {
for (const part of this.designConfig.parts) {
this.__resolvePart(part, distance)
this.__resolvePart([part], distance)
}
// Print final part distances.
for (const part of this.designConfig.parts) {
let qualifier = DISTANCE_DEBUG ? 'final' : ''
this.store.log.debug(
`⚪️ ${part.name} ${qualifier} options priority is __${
`⚪️ \`${part.name}\` ${qualifier} options priority is __${
this.__mutated.partDistance[part.name]
}__`
)
}
// for (const part of Object.values(this.__designParts)) this.__addPartConfig(part)
return this
}
@ -1354,35 +1321,10 @@ Pattern.prototype.__resolveParts = function (count = 0, distance = 0) {
* Resolves parts depdendencies into a flat array
*
* @private
* @param {object} graph - The graph is used to call itsels recursively
* @return {Pattern} this - The Pattern instance
*/
Pattern.prototype.__resolveDependencies = function (graph = false) {
if (!graph) graph = this.__dependencies
for (const i in this.__inject) {
const dependency = this.__inject[i]
if (typeof this.__dependencies[i] === 'undefined') this.__dependencies[i] = dependency
else if (this.__dependencies[i] !== dependency) {
if (typeof this.__dependencies[i] === 'string') {
this.__dependencies[i] = [this.__dependencies[i], dependency]
} else if (Array.isArray(this.__dependencies[i])) {
if (this.__dependencies[i].indexOf(dependency) === -1)
this.__dependencies[i].push(dependency)
} else {
this.store.log.error('Part dependencies should be a string or an array of strings')
throw new Error('Part dependencies should be a string or an array of strings')
}
}
}
let resolved = {}
let seen = {}
for (let part in graph) resolved[part] = this.__resolveDependency(seen, part, graph)
for (let part in seen) if (typeof resolved[part] === 'undefined') resolved[part] = []
this.__resolvedDependencies = resolved
this.config.resolvedDependencies = resolved
Pattern.prototype.__resolveDependencies = function () {
this.config.resolvedDependencies = this.__resolvedDependencies
return this
}
@ -1563,19 +1505,7 @@ Pattern.prototype.__wants = function (partName, set = 0) {
*/
function mergeDependencies(dep = [], current = []) {
// Current dependencies
const list = []
if (Array.isArray(current)) list.push(...current)
else if (typeof current === 'string') list.push(current)
const list = [].concat(current, dep)
if (Array.isArray(dep)) list.push(...dep)
else if (typeof dep === 'string') list.push(dep)
// Dependencies should be parts names (string) not the object
const deps = []
for (const part of [...new Set(list)]) {
if (typeof part === 'object') deps.push(part.name)
else deps.push(part)
}
return deps
return [...new Set(list)]
}

View file

@ -324,10 +324,10 @@ describe('Pattern', () => {
expect(pattern.config.options.optionR.list[1]).to.equal('green')
expect(pattern.config.options.optionR.list[2]).to.equal('blue')
// Dependencies
expect(pattern.__dependencies.partB[0]).to.equal('partA')
expect(pattern.__dependencies.partC[0]).to.equal('partB')
expect(pattern.__dependencies.partR[0]).to.equal('partC')
expect(pattern.__dependencies.partR[1]).to.equal('partA')
expect(pattern.__dependencies.partB).to.include('partA')
expect(pattern.__dependencies.partC).to.include('partB')
expect(pattern.__dependencies.partR).to.include('partC')
expect(pattern.__dependencies.partR).to.include('partA')
// Inject
expect(pattern.__inject.partB).to.equal('partA')
expect(pattern.__inject.partC).to.equal('partB')