222 lines
4.8 KiB
Go
222 lines
4.8 KiB
Go
// SPDX-License-Identifier: EUPL-1.2
|
|
|
|
package primitives
|
|
|
|
import (
|
|
"bytes"
|
|
"encoding/binary"
|
|
"fmt"
|
|
"sort"
|
|
)
|
|
|
|
// NameStateGetter is the minimal lookup interface used by NameView.
|
|
type NameStateGetter interface {
|
|
GetNameState(Hash) (*NameState, error)
|
|
}
|
|
|
|
// NameView caches name states during state-tree processing.
|
|
type NameView struct {
|
|
names map[Hash]*NameState
|
|
order []Hash
|
|
}
|
|
|
|
// NewNameView creates an empty name-state view.
|
|
func NewNameView() *NameView {
|
|
return &NameView{
|
|
names: make(map[Hash]*NameState),
|
|
}
|
|
}
|
|
|
|
func (v *NameView) ensureCache() {
|
|
if v.names == nil {
|
|
v.names = make(map[Hash]*NameState)
|
|
}
|
|
}
|
|
|
|
// GetNameStateSync returns the cached name state or loads it from db.
|
|
//
|
|
// When the database has no entry for the requested hash, a new empty state is
|
|
// created with NameHash populated, mirroring the JS reference behavior.
|
|
func (v *NameView) GetNameStateSync(db NameStateGetter, nameHash Hash) (*NameState, error) {
|
|
v.ensureCache()
|
|
|
|
if ns, ok := v.names[nameHash]; ok {
|
|
return ns, nil
|
|
}
|
|
|
|
if db != nil {
|
|
ns, err := db.GetNameState(nameHash)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
if ns != nil {
|
|
v.names[nameHash] = ns
|
|
v.order = append(v.order, nameHash)
|
|
return ns, nil
|
|
}
|
|
}
|
|
|
|
ns := &NameState{NameHash: nameHash}
|
|
v.names[nameHash] = ns
|
|
v.order = append(v.order, nameHash)
|
|
return ns, nil
|
|
}
|
|
|
|
// GetNameState is an alias for GetNameStateSync.
|
|
func (v *NameView) GetNameState(db NameStateGetter, nameHash Hash) (*NameState, error) {
|
|
return v.GetNameStateSync(db, nameHash)
|
|
}
|
|
|
|
// ToNameUndo converts the cached view into a sparse undo record.
|
|
func (v *NameView) ToNameUndo() NameUndo {
|
|
undo := NameUndo{}
|
|
if v == nil {
|
|
return undo
|
|
}
|
|
return undo.FromView(v)
|
|
}
|
|
|
|
// NameUndoEntry pairs a name hash with its sparse state delta.
|
|
type NameUndoEntry struct {
|
|
NameHash Hash
|
|
Delta NameDelta
|
|
}
|
|
|
|
// NameUndo stores the deltas required to roll a name view back.
|
|
type NameUndo struct {
|
|
Names []NameUndoEntry
|
|
}
|
|
|
|
// FromView populates the undo record from a cached view.
|
|
func (u *NameUndo) FromView(view *NameView) NameUndo {
|
|
if view == nil {
|
|
*u = NameUndo{}
|
|
return *u
|
|
}
|
|
|
|
u.Names = u.Names[:0]
|
|
|
|
seen := make(map[Hash]struct{}, len(view.order))
|
|
|
|
appendEntry := func(hash Hash, ns *NameState) {
|
|
if ns == nil || !ns.HasDelta() {
|
|
return
|
|
}
|
|
|
|
u.Names = append(u.Names, NameUndoEntry{
|
|
NameHash: hash,
|
|
Delta: *ns.Delta(),
|
|
})
|
|
}
|
|
|
|
for _, hash := range view.order {
|
|
ns, ok := view.names[hash]
|
|
if !ok {
|
|
continue
|
|
}
|
|
|
|
seen[hash] = struct{}{}
|
|
appendEntry(hash, ns)
|
|
}
|
|
|
|
if len(seen) != len(view.names) {
|
|
remaining := make([]Hash, 0, len(view.names)-len(seen))
|
|
for hash := range view.names {
|
|
if _, ok := seen[hash]; ok {
|
|
continue
|
|
}
|
|
remaining = append(remaining, hash)
|
|
}
|
|
|
|
sort.Slice(remaining, func(i, j int) bool {
|
|
return bytes.Compare(remaining[i][:], remaining[j][:]) < 0
|
|
})
|
|
|
|
for _, hash := range remaining {
|
|
appendEntry(hash, view.names[hash])
|
|
}
|
|
}
|
|
|
|
return *u
|
|
}
|
|
|
|
// GetSize returns the serialized size of the undo payload.
|
|
func (u NameUndo) GetSize() int {
|
|
size := 4
|
|
for _, entry := range u.Names {
|
|
size += len(entry.NameHash)
|
|
size += entry.Delta.GetSize()
|
|
}
|
|
return size
|
|
}
|
|
|
|
// MarshalBinary serializes the undo payload.
|
|
func (u NameUndo) MarshalBinary() ([]byte, error) {
|
|
buf := make([]byte, 0, u.GetSize())
|
|
|
|
var tmp [4]byte
|
|
binary.LittleEndian.PutUint32(tmp[:], uint32(len(u.Names)))
|
|
buf = append(buf, tmp[:]...)
|
|
for _, entry := range u.Names {
|
|
buf = append(buf, entry.NameHash[:]...)
|
|
delta, err := entry.Delta.MarshalBinary()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("primitives.NameUndo.MarshalBinary: %w", err)
|
|
}
|
|
buf = append(buf, delta...)
|
|
}
|
|
|
|
return buf, nil
|
|
}
|
|
|
|
// UnmarshalBinary decodes the undo payload.
|
|
func (u *NameUndo) UnmarshalBinary(data []byte) error {
|
|
*u = NameUndo{}
|
|
|
|
if len(data) < 4 {
|
|
return fmt.Errorf("primitives.NameUndo.UnmarshalBinary: short buffer")
|
|
}
|
|
|
|
count := binary.LittleEndian.Uint32(data[:4])
|
|
data = data[4:]
|
|
|
|
u.Names = make([]NameUndoEntry, 0, int(count))
|
|
for i := uint32(0); i < count; i++ {
|
|
if len(data) < len(Hash{}) {
|
|
return fmt.Errorf("primitives.NameUndo.UnmarshalBinary: short buffer")
|
|
}
|
|
|
|
var hash Hash
|
|
copy(hash[:], data[:len(hash)])
|
|
data = data[len(hash):]
|
|
|
|
delta, consumed, err := decodeNameDeltaPrefix(data)
|
|
if err != nil {
|
|
return fmt.Errorf("primitives.NameUndo.UnmarshalBinary: %w", err)
|
|
}
|
|
|
|
u.Names = append(u.Names, NameUndoEntry{
|
|
NameHash: hash,
|
|
Delta: delta,
|
|
})
|
|
data = data[consumed:]
|
|
}
|
|
|
|
if len(data) != 0 {
|
|
return fmt.Errorf("primitives.NameUndo.UnmarshalBinary: trailing data")
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
func decodeNameDeltaPrefix(data []byte) (NameDelta, int, error) {
|
|
for n := 4; n <= len(data); n++ {
|
|
var delta NameDelta
|
|
if err := delta.UnmarshalBinary(data[:n]); err == nil {
|
|
return delta, n, nil
|
|
}
|
|
}
|
|
|
|
return NameDelta{}, 0, fmt.Errorf("short buffer")
|
|
}
|