148 lines
2.9 KiB
Go
148 lines
2.9 KiB
Go
package dirty
|
|
|
|
import "maps"
|
|
|
|
type MapEntry[K comparable, V Cloneable[V]] struct {
|
|
m *Map[K, V]
|
|
mapEntry[K, V]
|
|
}
|
|
|
|
func (e *MapEntry[K, V]) Change(apply func(V)) {
|
|
if e.delete {
|
|
panic("tried to change a deleted entry")
|
|
}
|
|
if !e.dirty {
|
|
e.value = e.value.Clone()
|
|
e.dirty = true
|
|
e.m.dirty[e.key] = e
|
|
}
|
|
apply(e.value)
|
|
}
|
|
|
|
func (e *MapEntry[K, V]) ChangeIf(cond func(V) bool, apply func(V)) bool {
|
|
if cond(e.Value()) {
|
|
e.Change(apply)
|
|
return true
|
|
}
|
|
return false
|
|
}
|
|
|
|
func (e *MapEntry[K, V]) Delete() {
|
|
if !e.dirty {
|
|
e.m.dirty[e.key] = e
|
|
}
|
|
e.delete = true
|
|
}
|
|
|
|
func (e *MapEntry[K, V]) Locked(fn func(Value[V])) {
|
|
fn(e)
|
|
}
|
|
|
|
type Map[K comparable, V Cloneable[V]] struct {
|
|
base map[K]V
|
|
dirty map[K]*MapEntry[K, V]
|
|
}
|
|
|
|
func NewMap[K comparable, V Cloneable[V]](base map[K]V) *Map[K, V] {
|
|
return &Map[K, V]{
|
|
base: base,
|
|
dirty: make(map[K]*MapEntry[K, V]),
|
|
}
|
|
}
|
|
|
|
func (m *Map[K, V]) Get(key K) (*MapEntry[K, V], bool) {
|
|
if entry, ok := m.dirty[key]; ok {
|
|
if entry.delete {
|
|
return nil, false
|
|
}
|
|
return entry, true
|
|
}
|
|
value, ok := m.base[key]
|
|
if !ok {
|
|
return nil, false
|
|
}
|
|
return &MapEntry[K, V]{
|
|
m: m,
|
|
mapEntry: mapEntry[K, V]{
|
|
key: key,
|
|
original: value,
|
|
value: value,
|
|
dirty: false,
|
|
},
|
|
}, true
|
|
}
|
|
|
|
// Add sets a new entry in the dirty map without checking if it exists
|
|
// in the base map. The entry added is considered dirty, so it should
|
|
// be a fresh value, mutable until finalized (i.e., it will not be cloned
|
|
// before changing if a change is made). If modifying an entry that may
|
|
// exist in the base map, use `Change` instead.
|
|
func (m *Map[K, V]) Add(key K, value V) {
|
|
m.dirty[key] = &MapEntry[K, V]{
|
|
m: m,
|
|
mapEntry: mapEntry[K, V]{
|
|
key: key,
|
|
value: value,
|
|
dirty: true,
|
|
},
|
|
}
|
|
}
|
|
|
|
func (m *Map[K, V]) Change(key K, apply func(V)) {
|
|
if entry, ok := m.Get(key); ok {
|
|
entry.Change(apply)
|
|
} else {
|
|
panic("tried to change a non-existent entry")
|
|
}
|
|
}
|
|
|
|
func (m *Map[K, V]) Delete(key K) {
|
|
if entry, ok := m.Get(key); ok {
|
|
entry.Delete()
|
|
} else {
|
|
panic("tried to delete a non-existent entry")
|
|
}
|
|
}
|
|
|
|
func (m *Map[K, V]) Range(fn func(*MapEntry[K, V]) bool) {
|
|
seenInDirty := make(map[K]struct{})
|
|
for _, entry := range m.dirty {
|
|
seenInDirty[entry.key] = struct{}{}
|
|
if !entry.delete && !fn(entry) {
|
|
break
|
|
}
|
|
}
|
|
for key, value := range m.base {
|
|
if _, ok := seenInDirty[key]; ok {
|
|
continue // already processed in dirty entries
|
|
}
|
|
if !fn(&MapEntry[K, V]{m: m, mapEntry: mapEntry[K, V]{
|
|
key: key,
|
|
original: value,
|
|
value: value,
|
|
dirty: false,
|
|
}}) {
|
|
break
|
|
}
|
|
}
|
|
}
|
|
|
|
func (m *Map[K, V]) Finalize() (result map[K]V, changed bool) {
|
|
if len(m.dirty) == 0 {
|
|
return m.base, false // no changes, return base map
|
|
}
|
|
if m.base == nil {
|
|
result = make(map[K]V, len(m.dirty))
|
|
} else {
|
|
result = maps.Clone(m.base)
|
|
}
|
|
for key, entry := range m.dirty {
|
|
if entry.delete {
|
|
delete(result, key)
|
|
} else {
|
|
result[key] = entry.value
|
|
}
|
|
}
|
|
return result, true
|
|
}
|