286 lines
10 KiB
Go
286 lines
10 KiB
Go
package engine
|
|
|
|
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
|
|
|
|
type historyInheritancePayload struct {
|
|
operations []inheritanceOperation
|
|
}
|
|
|
|
type inheritanceEvidenceKind uint8
|
|
|
|
const (
|
|
inheritanceEvidenceControl inheritanceEvidenceKind = iota
|
|
inheritanceEvidenceParent
|
|
inheritanceEvidenceChild
|
|
inheritanceEvidenceDisplaced
|
|
inheritanceEvidenceRemoval
|
|
)
|
|
|
|
type inheritanceAttributionSelection struct {
|
|
kind inheritanceEvidenceKind
|
|
path Path
|
|
origin Origin
|
|
dependencies []Path
|
|
}
|
|
|
|
type inheritanceShapeFacts struct {
|
|
child bool
|
|
parent bool
|
|
effective bool
|
|
}
|
|
|
|
// observeInheritanceEvidence owns the ordered inheritance operation list and
|
|
// records its concrete before/after shapes against the builder's one scratch
|
|
// projection. Parent and effective-child trees alone cannot establish which
|
|
// concrete paths an operation displaced from its child or enclosing tree.
|
|
func (builder *historyBuilder) observeInheritanceEvidence(operations []inheritanceOperation) {
|
|
builder.requireOpen()
|
|
if len(operations) == 0 {
|
|
return
|
|
}
|
|
payload := builder.nextPayload(historyPhaseInheritance)
|
|
if payload.id != len(builder.model.inheritancePayloads) {
|
|
panic("configkit: inheritance payload identifiers are out of order")
|
|
}
|
|
owned := copyInheritanceOperations(operations, false)
|
|
builder.model.inheritancePayloads = append(builder.model.inheritancePayloads, historyInheritancePayload{operations: owned})
|
|
|
|
for operationIndex, operation := range owned {
|
|
if operation.control != nil {
|
|
builder.observeInheritanceControl(payload, operationIndex, operation)
|
|
continue
|
|
}
|
|
if operation.transfer == nil {
|
|
continue
|
|
}
|
|
builder.observeInheritanceTransfer(payload, operationIndex, operation)
|
|
}
|
|
}
|
|
|
|
func (builder *historyBuilder) observeInheritanceControl(payload historyPayloadRef, operationIndex int, operation inheritanceOperation) {
|
|
control := operation.control
|
|
if control == nil {
|
|
return
|
|
}
|
|
node, before := builder.scratchAt(control.path)
|
|
selections := resolveInheritanceAttribution(operation, control.path)
|
|
var attributionRef historyAttributionRef = noHistoryAttribution
|
|
var sensitive bool
|
|
if before && node != nil {
|
|
sensitive = node.Sensitive
|
|
}
|
|
if len(selections) != 0 {
|
|
selection := selections[0]
|
|
attributionRef = builder.addAttribution(selection.origin, nil, selection.dependencies)
|
|
}
|
|
ref := payload
|
|
ref.substep = operationIndex * 2
|
|
builder.addObservation(historyObservation{
|
|
path: control.path, phase: historyPhaseInheritance, payload: ref,
|
|
reportedPath: control.path, role: historyRoleControl,
|
|
beforePresent: before, sensitive: sensitive,
|
|
attributionRef: attributionRef,
|
|
})
|
|
replaceHistoryPath(&builder.scratch, control.path, nil)
|
|
}
|
|
|
|
func (builder *historyBuilder) observeInheritanceTransfer(payload historyPayloadRef, operationIndex int, operation inheritanceOperation) {
|
|
transfer := operation.transfer
|
|
if transfer == nil {
|
|
return
|
|
}
|
|
beforeChild, _ := documentAtPath(builder.scratch, transfer.to)
|
|
parentPaths := inheritanceTreePaths(operation.parent, transfer.to)
|
|
beforePaths := inheritanceTreePaths(beforeChild, transfer.to)
|
|
parentSide := unionInheritancePaths(beforePaths, parentPaths)
|
|
parentRef := payload
|
|
parentRef.substep = operationIndex * 2
|
|
lookup := buildDocumentLookup(beforeChild, operation.parent)
|
|
for _, path := range parentSide {
|
|
if inheritanceOperationIdentity(operation, path) {
|
|
continue
|
|
}
|
|
beforeNode, before := documentAtInheritancePath(beforeChild, transfer.to, path, lookup)
|
|
parentNode, after := documentAtInheritancePath(operation.parent, transfer.to, path, lookup)
|
|
selection := selectInheritanceEvidence(resolveInheritanceAttribution(operation, path, inheritanceShapeFacts{
|
|
child: before, parent: after,
|
|
}), inheritanceEvidenceParent, inheritanceEvidenceDisplaced)
|
|
builder.addInheritanceObservation(parentRef, transfer.to, path, before, after, beforeNode, parentNode, selection)
|
|
}
|
|
|
|
parentWorking := document.Clone(operation.parent)
|
|
if parentWorking != nil {
|
|
replaceHistoryPath(&builder.scratch, transfer.to, parentWorking)
|
|
for _, field := range operation.identityFields {
|
|
identity, found := documentAtPath(operation.effective, Keys(field))
|
|
if found {
|
|
replaceHistoryPath(&builder.scratch, transfer.to.Key(field), document.Clone(identity))
|
|
}
|
|
}
|
|
}
|
|
parentState, _ := documentAtPath(builder.scratch, transfer.to)
|
|
effectivePaths := inheritanceTreePaths(operation.effective, transfer.to)
|
|
parentStatePaths := inheritanceTreePaths(parentState, transfer.to)
|
|
childSide := unionInheritancePaths(parentStatePaths, effectivePaths)
|
|
childRef := payload
|
|
childRef.substep = operationIndex*2 + 1
|
|
lookup = buildDocumentLookup(parentState, operation.effective)
|
|
for _, path := range childSide {
|
|
if inheritanceOperationIdentity(operation, path) {
|
|
continue
|
|
}
|
|
parentNode, before := documentAtInheritancePath(parentState, transfer.to, path, lookup)
|
|
effectiveNode, after := documentAtInheritancePath(operation.effective, transfer.to, path, lookup)
|
|
selection := selectInheritanceEvidence(resolveInheritanceAttribution(operation, path, inheritanceShapeFacts{
|
|
parent: before, effective: after,
|
|
}), inheritanceEvidenceChild, inheritanceEvidenceRemoval, inheritanceEvidenceDisplaced)
|
|
builder.addInheritanceObservation(childRef, transfer.to, path, before, after, parentNode, effectiveNode, selection)
|
|
}
|
|
|
|
if operation.effective != nil {
|
|
replaceHistoryPath(&builder.scratch, transfer.to, document.Clone(operation.effective))
|
|
}
|
|
}
|
|
|
|
func (builder *historyBuilder) addInheritanceObservation(payload historyPayloadRef, reportedPath, path Path, before, after bool, beforeNode, afterNode *document.Node, selection *inheritanceAttributionSelection) {
|
|
ref := noHistoryAttribution
|
|
if selection != nil {
|
|
ref = builder.addAttribution(selection.origin, nil, selection.dependencies)
|
|
}
|
|
sensitive := beforeNode != nil && beforeNode.Sensitive || afterNode != nil && afterNode.Sensitive
|
|
builder.addObservation(historyObservation{
|
|
path: path, phase: historyPhaseInheritance, payload: payload,
|
|
reportedPath: reportedPath, role: historyRoleOperation,
|
|
beforePresent: before, afterPresent: after, sensitive: sensitive,
|
|
attributionRef: ref,
|
|
})
|
|
}
|
|
|
|
func selectInheritanceEvidence(selections []inheritanceAttributionSelection, preferred inheritanceEvidenceKind, fallbacks ...inheritanceEvidenceKind) *inheritanceAttributionSelection {
|
|
for index := range selections {
|
|
if selections[index].kind == preferred {
|
|
return &selections[index]
|
|
}
|
|
}
|
|
for _, fallback := range fallbacks {
|
|
for index := range selections {
|
|
if selections[index].kind == fallback {
|
|
return &selections[index]
|
|
}
|
|
}
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func inheritanceTreePaths(root *document.Node, destination Path) []Path {
|
|
if root == nil {
|
|
return nil
|
|
}
|
|
var paths []Path
|
|
walkDocument(root, Path{}, func(local Path, _ *document.Node) {
|
|
paths = append(paths, appendInheritancePath(destination, local))
|
|
})
|
|
return paths
|
|
}
|
|
|
|
func unionInheritancePaths(groups ...[]Path) []Path {
|
|
var paths []Path
|
|
seen := make(map[string][]Path)
|
|
for _, group := range groups {
|
|
for _, path := range group {
|
|
found := false
|
|
for _, existing := range seen[path.String()] {
|
|
if sameConcreteHistoryPath(existing, path) {
|
|
found = true
|
|
break
|
|
}
|
|
}
|
|
if !found {
|
|
owned := path.clone()
|
|
paths = append(paths, owned)
|
|
seen[path.String()] = append(seen[path.String()], owned)
|
|
}
|
|
}
|
|
}
|
|
return paths
|
|
}
|
|
|
|
func documentAtInheritancePath(root *document.Node, rootPath, path Path, lookup *documentLookup) (*document.Node, bool) {
|
|
relative, within := relativeHistoryPath(rootPath, path)
|
|
if !within {
|
|
return nil, false
|
|
}
|
|
return documentAtPathWithLookup(root, relative, lookup)
|
|
}
|
|
|
|
func resolveInheritanceAttribution(operation inheritanceOperation, destination Path, shapes ...inheritanceShapeFacts) []inheritanceAttributionSelection {
|
|
if operation.control != nil {
|
|
if !pathsIntersect(operation.control.path, destination) {
|
|
return nil
|
|
}
|
|
origin := cloneOrigin(operation.controlOrigin)
|
|
origin.Operation = ChangeInheritance
|
|
origin.Inheritance = projectInheritanceSteps(origin)
|
|
return []inheritanceAttributionSelection{{
|
|
kind: inheritanceEvidenceControl, path: operation.control.path.clone(), origin: origin,
|
|
dependencies: []Path{operation.control.path.clone()},
|
|
}}
|
|
}
|
|
if operation.transfer == nil || !pathsIntersect(operation.transfer.to, destination) || inheritanceOperationIdentity(operation, destination) {
|
|
return nil
|
|
}
|
|
path := destination
|
|
if pathContains(destination, operation.transfer.to) {
|
|
path = operation.transfer.to
|
|
}
|
|
var selections []inheritanceAttributionSelection
|
|
parent, parentFound := transferredOperationOrigin(operation, path)
|
|
effective, effectiveFound := operation.effectiveOrigins[path.String()]
|
|
child, childFound := operation.childOrigins[path.String()]
|
|
if len(shapes) != 0 {
|
|
parentFound = parentFound && shapes[0].parent
|
|
effectiveFound = effectiveFound && shapes[0].effective
|
|
childFound = childFound && shapes[0].child
|
|
}
|
|
if parentFound {
|
|
parent.Operation = ChangeInheritance
|
|
parent.Inheritance = projectInheritanceSteps(parent)
|
|
selections = append(selections, inheritanceAttributionSelection{
|
|
kind: inheritanceEvidenceParent, path: path.clone(), origin: parent,
|
|
dependencies: []Path{operation.transfer.from.clone(), operation.transfer.reference.clone()},
|
|
})
|
|
}
|
|
if effectiveFound {
|
|
effective = cloneOrigin(effective)
|
|
effective.Operation = ChangeInheritance
|
|
}
|
|
if effectiveFound && (!parentFound || !originContainedBy(effective, parent)) {
|
|
effective.Inheritance = projectInheritanceSteps(effective)
|
|
selections = append(selections, inheritanceAttributionSelection{
|
|
kind: inheritanceEvidenceChild, path: path.clone(), origin: effective,
|
|
dependencies: []Path{operation.transfer.reference.clone()},
|
|
})
|
|
} else if parentFound && !effectiveFound {
|
|
removed, found := operation.effectiveOrigins[operation.transfer.to.String()]
|
|
if found {
|
|
removed = cloneOrigin(removed)
|
|
removed.Operation = ChangeInheritance
|
|
removed.Inheritance = projectInheritanceSteps(removed)
|
|
selections = append(selections, inheritanceAttributionSelection{
|
|
kind: inheritanceEvidenceRemoval, path: operation.transfer.to.clone(), origin: removed,
|
|
dependencies: []Path{operation.transfer.reference.clone()},
|
|
})
|
|
}
|
|
}
|
|
if childFound && !parentFound {
|
|
child = cloneOrigin(child)
|
|
child.Operation = ChangeInheritance
|
|
child.Inheritance = projectInheritanceSteps(child)
|
|
selections = append(selections, inheritanceAttributionSelection{
|
|
kind: inheritanceEvidenceDisplaced, path: path.clone(), origin: child,
|
|
dependencies: []Path{operation.transfer.reference.clone()},
|
|
})
|
|
}
|
|
return selections
|
|
}
|