101 lines
2.9 KiB
Go
101 lines
2.9 KiB
Go
// Package npcinteractions owns canonical ordering and exact-identity rules for
|
|
// D&D NPC interaction artifacts.
|
|
package npcinteractions
|
|
|
|
import (
|
|
"strconv"
|
|
"strings"
|
|
|
|
"gitea.maximumdirect.net/eric/notarius/internal/core/source"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/npcs/identity"
|
|
"gitea.maximumdirect.net/eric/notarius/internal/modules/dnd/shared"
|
|
)
|
|
|
|
// SourceRefsEqual reports whether two source-reference lists have identical
|
|
// representations and values.
|
|
func SourceRefsEqual(left, right []source.SourceRef) bool {
|
|
if (left == nil) != (right == nil) || len(left) != len(right) {
|
|
return false
|
|
}
|
|
for index := range left {
|
|
if left[index] != right[index] {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// Less defines the canonical order for NPC interaction occurrences.
|
|
func Less(order shared.SourceRefOrder, left, right dnd.NPCInteraction) bool {
|
|
leftPosition, leftHasEvidence := order.EarliestValid(left.SourceRefs)
|
|
rightPosition, rightHasEvidence := order.EarliestValid(right.SourceRefs)
|
|
if leftHasEvidence != rightHasEvidence {
|
|
return leftHasEvidence
|
|
}
|
|
if leftHasEvidence && leftPosition != rightPosition {
|
|
return leftPosition < rightPosition
|
|
}
|
|
leftKey := identity.ComparisonKey(left.Name)
|
|
rightKey := identity.ComparisonKey(right.Name)
|
|
if leftKey != rightKey {
|
|
return leftKey < rightKey
|
|
}
|
|
if left.Name != right.Name {
|
|
return left.Name < right.Name
|
|
}
|
|
if left.Kind != right.Kind {
|
|
return left.Kind < right.Kind
|
|
}
|
|
return sourceRefsLess(order, left.SourceRefs, right.SourceRefs)
|
|
}
|
|
|
|
// ValidSourceRefs reports whether an interaction has non-empty, valid
|
|
// current-document evidence.
|
|
func ValidSourceRefs(index source.DocumentIndex, refs []source.SourceRef) bool {
|
|
if len(refs) == 0 {
|
|
return false
|
|
}
|
|
for _, ref := range refs {
|
|
if index.ValidateRef(ref) != nil {
|
|
return false
|
|
}
|
|
}
|
|
return true
|
|
}
|
|
|
|
// ExactIdentity returns a collision-safe key over every durable interaction
|
|
// field. Callers decide whether the record is eligible for duplicate handling.
|
|
func ExactIdentity(interaction dnd.NPCInteraction) string {
|
|
var key strings.Builder
|
|
writeKeyString(&key, interaction.Name)
|
|
writeKeyString(&key, string(interaction.Kind))
|
|
for _, ref := range interaction.SourceRefs {
|
|
writeKeyString(&key, ref.SourceID)
|
|
writeKeyInt(&key, ref.StartUnitID)
|
|
writeKeyInt(&key, ref.EndUnitID)
|
|
}
|
|
return key.String()
|
|
}
|
|
|
|
func sourceRefsLess(order shared.SourceRefOrder, left, right []source.SourceRef) bool {
|
|
for index := 0; index < len(left) && index < len(right); index++ {
|
|
if left[index] == right[index] {
|
|
continue
|
|
}
|
|
return order.Less(left[index], right[index])
|
|
}
|
|
return len(left) < len(right)
|
|
}
|
|
|
|
func writeKeyString(builder *strings.Builder, value string) {
|
|
builder.WriteString(strconv.Itoa(len(value)))
|
|
builder.WriteByte(':')
|
|
builder.WriteString(value)
|
|
}
|
|
|
|
func writeKeyInt(builder *strings.Builder, value int) {
|
|
builder.WriteString(strconv.Itoa(value))
|
|
builder.WriteByte(';')
|
|
}
|