Files
ConfigKit/internal/engine/sequence_history_projection.go

112 lines
4.1 KiB
Go

package engine
import "gitea.maximumdirect.net/eric/ConfigKit/internal/document"
// projectTierHistoryApplications composes each strict source's local projection
// with the tier operation. The retained source values stay in authored coordinates.
func projectTierHistoryApplications(applications []historyApplication, operations []sequenceHistoryContribution) []historyApplication {
mapped := false
for _, operation := range operations {
mapped = mapped || operation.mode == sequenceHistoryMapped
}
if !mapped {
return applications
}
projections := indexSequenceHistoryContributions(operations)
projected := cloneHistoryApplications(applications)
for index, original := range applications {
application := &projected[index]
var originalLookup *documentLookup
if path, found := projectSequenceOperationPath(original.path, projections); found {
application.path = path
}
for sequenceIndex, source := range original.sequences {
container := appendDestination(original.path, source.path)
operation, active := projections[container.String()]
if !active || operation.mode != sequenceHistoryMapped {
continue
}
if originalLookup == nil {
originalLookup = buildDocumentLookup(original.value)
}
value, found := documentAtPathWithLookup(original.value, source.path, originalLookup)
if !found || value.Kind != document.Sequence {
continue
}
sourceIndex := application.sequencesByPath[source.path.String()]
contribution := sequenceHistoryContribution{path: source.path.clone(), mode: sequenceHistoryMapped}
for inputIndex := range value.Sequence {
tierIndex := source.offset + inputIndex
if source.mode == sequenceHistoryMapped {
tierIndex = sourceIndex.outputForInput(inputIndex)
}
outputIndex, present := operation.projection.outputForInput(tierIndex)
if !present {
continue
}
contribution.items = append(contribution.items, sequenceItemProjection{inputIndex: inputIndex, outputIndex: outputIndex})
if _, replaced := operation.replacedOutputs[outputIndex]; replaced {
// Upsert replacements retain their lower-list position.
contribution.replacements = append(contribution.replacements, sequenceItemReplacement{
lowerIndex: outputIndex, higherIndex: inputIndex, outputIndex: outputIndex,
})
}
}
application.sequences[sequenceIndex] = contribution
}
application.sequencesByPath = indexSequenceHistoryContributions(application.sequences)
}
return projected
}
func projectSequenceOperationPath(path Path, projections map[string]sequenceHistoryContributionIndex) (Path, bool) {
projected := path.clone()
for index, segment := range path.segments {
if segment.kind != pathSegmentIndex {
continue
}
prefix := Path{segments: path.segments[:index]}
operation, found := projections[prefix.String()]
if !found || operation.mode != sequenceHistoryMapped {
continue
}
outputIndex, found := operation.projection.outputForInput(segment.index)
if !found {
return Path{}, false
}
projected.segments[index].index = outputIndex
}
return projected, true
}
// Project only incoming sources. Lower/displaced sources retain the coordinates
// of their own historical contribution, rather than becoming incoming survivors.
func projectTierSourceDestinations(sources []SourceInfo, versions []*sourceVersionEvidence, operations []sequenceHistoryContribution) {
if len(operations) == 0 {
return
}
projections := indexSequenceHistoryContributions(operations)
for index := range sources {
if path, found := projectSequenceOperationPath(sources[index].Destination, projections); found {
sources[index].Destination = path
}
}
for _, evidence := range versions {
if evidence == nil {
continue
}
paths := evidence.sensitivityPaths()
projected := make([]Path, len(paths))
for index, path := range paths {
projected[index] = path.clone()
if destination, found := projectSequenceOperationPath(path, projections); found {
projected[index] = destination
}
}
evidence.projectedPaths = projected
if path, found := projectSequenceOperationPath(evidence.destination, projections); found {
evidence.destination = path
}
}
}