mirror of
				https://github.com/superseriousbusiness/gotosocial.git
				synced 2025-10-31 02:12:24 -05:00 
			
		
		
		
	
		
			
	
	
		
			290 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
		
		
			
		
	
	
			290 lines
		
	
	
	
		
			5.7 KiB
		
	
	
	
		
			Go
		
	
	
	
	
	
|  | package maps | ||
|  | 
 | ||
|  | import ( | ||
|  | 	"fmt" | ||
|  | 	"reflect" | ||
|  | 
 | ||
|  | 	"codeberg.org/gruf/go-byteutil" | ||
|  | 	"codeberg.org/gruf/go-kv" | ||
|  | ) | ||
|  | 
 | ||
|  | // ordered provides a common ordered hashmap base, storing order in a doubly-linked list. | ||
|  | type ordered[K comparable, V any] struct { | ||
|  | 	hmap map[K]*elem[K, V] | ||
|  | 	list list[K, V] | ||
|  | 	pool []*elem[K, V] | ||
|  | 	rnly bool | ||
|  | } | ||
|  | 
 | ||
|  | // write_check panics if map is not in a safe-state to write to. | ||
|  | func (m ordered[K, V]) write_check() { | ||
|  | 	if m.rnly { | ||
|  | 		panic("map write during read loop") | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Has returns whether key exists in map. | ||
|  | func (m *ordered[K, V]) Has(key K) bool { | ||
|  | 	_, ok := m.hmap[key] | ||
|  | 	return ok | ||
|  | } | ||
|  | 
 | ||
|  | // Delete will delete given key from map, returns false if not found. | ||
|  | func (m *ordered[K, V]) Delete(key K) bool { | ||
|  | 	// Ensure safe | ||
|  | 	m.write_check() | ||
|  | 
 | ||
|  | 	// Look for existing elem | ||
|  | 	elem, ok := m.hmap[key] | ||
|  | 	if !ok { | ||
|  | 		return false | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Drop from list | ||
|  | 	m.list.Unlink(elem) | ||
|  | 
 | ||
|  | 	// Delete from map | ||
|  | 	delete(m.hmap, key) | ||
|  | 
 | ||
|  | 	// Return to pool | ||
|  | 	m.free(elem) | ||
|  | 
 | ||
|  | 	return true | ||
|  | } | ||
|  | 
 | ||
|  | // Range passes given function over the requested range of the map. | ||
|  | func (m *ordered[K, V]) Range(start, length int, fn func(int, K, V)) { | ||
|  | 	// Disallow writes | ||
|  | 	m.rnly = true | ||
|  | 	defer func() { | ||
|  | 		m.rnly = false | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	// Nil check | ||
|  | 	_ = fn | ||
|  | 
 | ||
|  | 	switch end := start + length; { | ||
|  | 	// No loop to iterate | ||
|  | 	case length == 0: | ||
|  | 		if start < 0 || (m.list.len > 0 && start >= m.list.len) { | ||
|  | 			panic("index out of bounds") | ||
|  | 		} | ||
|  | 
 | ||
|  | 	// Step backwards | ||
|  | 	case length < 0: | ||
|  | 		// Check loop indices are within map bounds | ||
|  | 		if end < -1 || start >= m.list.len || m.list.len == 0 { | ||
|  | 			panic("index out of bounds") | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Get starting index elem | ||
|  | 		elem := m.list.Index(start) | ||
|  | 
 | ||
|  | 		for i := start; i > end; i-- { | ||
|  | 			fn(i, elem.K, elem.V) | ||
|  | 			elem = elem.prev | ||
|  | 		} | ||
|  | 
 | ||
|  | 	// Step forwards | ||
|  | 	case length > 0: | ||
|  | 		// Check loop indices are within map bounds | ||
|  | 		if start < 0 || end > m.list.len || m.list.len == 0 { | ||
|  | 			panic("index out of bounds") | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Get starting index elem | ||
|  | 		elem := m.list.Index(start) | ||
|  | 
 | ||
|  | 		for i := start; i < end; i++ { | ||
|  | 			fn(i, elem.K, elem.V) | ||
|  | 			elem = elem.next | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // RangeIf passes given function over the requested range of the map. Returns early on 'fn' -> false. | ||
|  | func (m *ordered[K, V]) RangeIf(start, length int, fn func(int, K, V) bool) { | ||
|  | 	// Disallow writes | ||
|  | 	m.rnly = true | ||
|  | 	defer func() { | ||
|  | 		m.rnly = false | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	// Nil check | ||
|  | 	_ = fn | ||
|  | 
 | ||
|  | 	switch end := start + length; { | ||
|  | 	// No loop to iterate | ||
|  | 	case length == 0: | ||
|  | 		if start < 0 || (m.list.len > 0 && start >= m.list.len) { | ||
|  | 			panic("index out of bounds") | ||
|  | 		} | ||
|  | 
 | ||
|  | 	// Step backwards | ||
|  | 	case length < 0: | ||
|  | 		// Check loop indices are within map bounds | ||
|  | 		if end < -1 || start >= m.list.len || m.list.len == 0 { | ||
|  | 			panic("index out of bounds") | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Get starting index elem | ||
|  | 		elem := m.list.Index(start) | ||
|  | 
 | ||
|  | 		for i := start; i > end; i-- { | ||
|  | 			if !fn(i, elem.K, elem.V) { | ||
|  | 				return | ||
|  | 			} | ||
|  | 			elem = elem.prev | ||
|  | 		} | ||
|  | 
 | ||
|  | 	// Step forwards | ||
|  | 	case length > 0: | ||
|  | 		// Check loop indices are within map bounds | ||
|  | 		if start < 0 || end > m.list.len || m.list.len == 0 { | ||
|  | 			panic("index out of bounds") | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Get starting index elem | ||
|  | 		elem := m.list.Index(start) | ||
|  | 
 | ||
|  | 		for i := start; i < end; i++ { | ||
|  | 			if !fn(i, elem.K, elem.V) { | ||
|  | 				return | ||
|  | 			} | ||
|  | 			elem = elem.next | ||
|  | 		} | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Truncate will truncate the map from the back by given amount, passing dropped elements to given function. | ||
|  | func (m *ordered[K, V]) Truncate(sz int, fn func(K, V)) { | ||
|  | 	// Check size withing bounds | ||
|  | 	if sz > m.list.len { | ||
|  | 		panic("index out of bounds") | ||
|  | 	} | ||
|  | 
 | ||
|  | 	if fn == nil { | ||
|  | 		// move nil check out of loop | ||
|  | 		fn = func(K, V) {} | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Disallow writes | ||
|  | 	m.rnly = true | ||
|  | 	defer func() { | ||
|  | 		m.rnly = false | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	for i := 0; i < sz; i++ { | ||
|  | 		// Pop current tail | ||
|  | 		elem := m.list.tail | ||
|  | 		m.list.Unlink(elem) | ||
|  | 
 | ||
|  | 		// Delete from map | ||
|  | 		delete(m.hmap, elem.K) | ||
|  | 
 | ||
|  | 		// Pass dropped to func | ||
|  | 		fn(elem.K, elem.V) | ||
|  | 
 | ||
|  | 		// Release to pool | ||
|  | 		m.free(elem) | ||
|  | 	} | ||
|  | } | ||
|  | 
 | ||
|  | // Len returns the current length of the map. | ||
|  | func (m *ordered[K, V]) Len() int { | ||
|  | 	return m.list.len | ||
|  | } | ||
|  | 
 | ||
|  | // format implements fmt.Formatter, allowing performant string formatting of map. | ||
|  | func (m *ordered[K, V]) format(rtype reflect.Type, state fmt.State, verb rune) { | ||
|  | 	var ( | ||
|  | 		kvbuf byteutil.Buffer | ||
|  | 		field kv.Field | ||
|  | 		vbose bool | ||
|  | 	) | ||
|  | 
 | ||
|  | 	switch { | ||
|  | 	// Only handle 'v' verb | ||
|  | 	case verb != 'v': | ||
|  | 		panic("invalid verb '" + string(verb) + "' for map") | ||
|  | 
 | ||
|  | 	// Prefix with type when verbose | ||
|  | 	case state.Flag('#'): | ||
|  | 		state.Write([]byte(rtype.String())) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Disallow writes | ||
|  | 	m.rnly = true | ||
|  | 	defer func() { | ||
|  | 		m.rnly = false | ||
|  | 	}() | ||
|  | 
 | ||
|  | 	// Write map opening brace | ||
|  | 	state.Write([]byte{'{'}) | ||
|  | 
 | ||
|  | 	if m.list.len > 0 { | ||
|  | 		// Preallocate buffer | ||
|  | 		kvbuf.Guarantee(64) | ||
|  | 
 | ||
|  | 		// Start at index 0 | ||
|  | 		elem := m.list.head | ||
|  | 
 | ||
|  | 		for i := 0; i < m.list.len-1; i++ { | ||
|  | 			// Append formatted key-val pair to state | ||
|  | 			field.K = fmt.Sprint(elem.K) | ||
|  | 			field.V = elem.V | ||
|  | 			field.AppendFormat(&kvbuf, vbose) | ||
|  | 			_, _ = state.Write(kvbuf.B) | ||
|  | 			kvbuf.Reset() | ||
|  | 
 | ||
|  | 			// Prepare buffer with comma separator | ||
|  | 			kvbuf.B = append(kvbuf.B, `, `...) | ||
|  | 
 | ||
|  | 			// Jump to next in list | ||
|  | 			elem = elem.next | ||
|  | 		} | ||
|  | 
 | ||
|  | 		// Append formatted key-val pair to state | ||
|  | 		field.K = fmt.Sprint(elem.K) | ||
|  | 		field.V = elem.V | ||
|  | 		field.AppendFormat(&kvbuf, vbose) | ||
|  | 		_, _ = state.Write(kvbuf.B) | ||
|  | 	} | ||
|  | 
 | ||
|  | 	// Write map closing brace | ||
|  | 	state.Write([]byte{'}'}) | ||
|  | } | ||
|  | 
 | ||
|  | // Std returns a clone of map's data in the standard library equivalent map type. | ||
|  | func (m *ordered[K, V]) Std() map[K]V { | ||
|  | 	std := make(map[K]V, m.list.len) | ||
|  | 	for _, elem := range m.hmap { | ||
|  | 		std[elem.K] = elem.V | ||
|  | 	} | ||
|  | 	return std | ||
|  | } | ||
|  | 
 | ||
|  | // alloc will acquire list element from pool, or allocate new. | ||
|  | func (m *ordered[K, V]) alloc() *elem[K, V] { | ||
|  | 	if len(m.pool) == 0 { | ||
|  | 		return &elem[K, V]{} | ||
|  | 	} | ||
|  | 	idx := len(m.pool) - 1 | ||
|  | 	elem := m.pool[idx] | ||
|  | 	m.pool = m.pool[:idx] | ||
|  | 	return elem | ||
|  | } | ||
|  | 
 | ||
|  | // free will reset elem fields and place back in pool. | ||
|  | func (m *ordered[K, V]) free(elem *elem[K, V]) { | ||
|  | 	var ( | ||
|  | 		zk K | ||
|  | 		zv V | ||
|  | 	) | ||
|  | 	elem.K = zk | ||
|  | 	elem.V = zv | ||
|  | 	elem.next = nil | ||
|  | 	elem.prev = nil | ||
|  | 	m.pool = append(m.pool, elem) | ||
|  | } |