Files
Piik/internal/server/ordered/map.go
T
AAA da47189fc3 refactor: unify Go runtime and audited frontend
Squash integration of the reviewed Go core, frontend audit fixes, release tooling, and deployment hygiene. CI passed all Server/Client jobs.
2026-09-06 23:26:01 +08:00

155 lines
4.0 KiB
Go

// Package ordered provides an insertion-ordered map with the iteration
// semantics of a JavaScript Map. The TypeScript server relied on those
// semantics wherever it iterated a Map or Set: wire-visible participant order,
// candidate ordering, round-robin state, and delete-during-iteration loops.
package ordered
import "iter"
type entry[K comparable, V any] struct {
key K
value V
deleted bool
}
// Map keeps keys in first-insertion order. Set on an existing key keeps its
// position; MoveToBack reproduces the JavaScript delete-then-set idiom.
// Iteration is live: an entry deleted before it is visited is skipped and an
// entry appended during iteration is visited, exactly like a JavaScript Map.
// The zero value is ready to use.
type Map[K comparable, V any] struct {
entries []entry[K, V]
index map[K]int
deleted int
iterating int
}
// Len reports the number of live entries.
func (m *Map[K, V]) Len() int { return len(m.index) }
// Get returns the value for key and whether it is present.
func (m *Map[K, V]) Get(key K) (V, bool) {
if position, ok := m.index[key]; ok {
return m.entries[position].value, true
}
var zero V
return zero, false
}
// Has reports whether key is present.
func (m *Map[K, V]) Has(key K) bool {
_, ok := m.index[key]
return ok
}
// Set stores value under key, keeping the existing position when the key is
// already present and appending otherwise.
func (m *Map[K, V]) Set(key K, value V) {
if position, ok := m.index[key]; ok {
m.entries[position].value = value
return
}
if m.index == nil {
m.index = make(map[K]int)
}
m.index[key] = len(m.entries)
m.entries = append(m.entries, entry[K, V]{key: key, value: value})
}
// MoveToBack re-inserts key at the end, as JavaScript `map.delete(k);
// map.set(k, v)` does. It is a plain Set when the key is absent.
func (m *Map[K, V]) MoveToBack(key K, value V) {
m.Delete(key)
m.Set(key, value)
}
// Delete removes key and reports whether it was present.
func (m *Map[K, V]) Delete(key K) bool {
position, ok := m.index[key]
if !ok {
return false
}
delete(m.index, key)
var zero V
m.entries[position].value = zero
m.entries[position].deleted = true
m.deleted++
m.compact()
return true
}
// Clear removes every entry. Dropping the index before compacting is safe
// because every entry is now a tombstone, so compact writes no index entry.
func (m *Map[K, V]) Clear() {
for position := range m.entries {
if !m.entries[position].deleted {
var zero V
m.entries[position].value = zero
m.entries[position].deleted = true
m.deleted++
}
}
m.index = nil
m.compact()
}
// Keys returns a snapshot of the live keys in order.
func (m *Map[K, V]) Keys() []K {
keys := make([]K, 0, len(m.index))
for _, current := range m.entries {
if !current.deleted {
keys = append(keys, current.key)
}
}
return keys
}
// Values returns a snapshot of the live values in order.
func (m *Map[K, V]) Values() []V {
values := make([]V, 0, len(m.index))
for _, current := range m.entries {
if !current.deleted {
values = append(values, current.value)
}
}
return values
}
// All iterates live entries in order with JavaScript Map semantics. Mutating
// the map inside the loop is allowed.
func (m *Map[K, V]) All() iter.Seq2[K, V] {
return func(yield func(K, V) bool) {
m.iterating++
defer func() {
m.iterating--
m.compact()
}()
for position := 0; position < len(m.entries); position++ {
current := m.entries[position]
if current.deleted {
continue
}
if !yield(current.key, current.value) {
return
}
}
}
}
// compact drops tombstones once they dominate, but never while an iteration
// is walking the entry slice.
func (m *Map[K, V]) compact() {
if m.iterating > 0 || m.deleted == 0 || m.deleted*2 < len(m.entries) {
return
}
live := make([]entry[K, V], 0, len(m.index))
for _, current := range m.entries {
if !current.deleted {
m.index[current.key] = len(live)
live = append(live, current)
}
}
m.entries = live
m.deleted = 0
}