Migrate pkg/lazy => pkglib/{lazy,lifetime}

This commit is contained in:
Tom Wiesing 2023-02-26 10:00:47 +01:00
parent c3ca8e2974
commit 5e89fadeeb
No known key found for this signature in database
21 changed files with 54 additions and 612 deletions

View file

@ -6,8 +6,8 @@ import (
"sync"
"time"
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
"github.com/gorilla/websocket"
"github.com/tkw1536/pkglib/lazy"
)
// WebSocket implements serving a WebSocket

View file

@ -1,50 +0,0 @@
package lazy
import (
"sync"
)
// Lazy holds a lazily initialized value of T.
// A non-zero lazy must not be copied after first use.
type Lazy[T any] struct {
once sync.Once
m sync.RWMutex // m protects setting the value of this T
value T // the stored value
}
// Get returns the value associated with this Lazy.
//
// If no other call to Get has started or completed an initialization, calls init to initialize the value.
// A nil init function indicates to store the zero value of T.
// If an initialization has been previously completed, the previously stored value is returned.
//
// If init panics, the initization is considered to be completed.
// Future calls to Get() do not invoke init, and the zero value of T is returned.
//
// Get may safely be called concurrently.
func (lazy *Lazy[T]) Get(init func() T) T {
lazy.m.RLock()
defer lazy.m.RUnlock()
lazy.once.Do(func() {
if init != nil {
lazy.value = init()
}
})
return lazy.value
}
// Set atomically sets the value of this lazy.
// Any previously set value will be overwritten.
// Future calls to [Get] will not invoke init.
//
// It may be called concurrently with calls to [Get].
func (lazy *Lazy[T]) Set(value T) {
lazy.m.Lock()
defer lazy.m.Unlock()
lazy.value = value
lazy.once.Do(func() {})
}

View file

@ -1,31 +0,0 @@
package lazy
import "fmt"
func ExampleLazy() {
var lazy Lazy[int]
// the first invocation to lazy will be called and set the value
fmt.Println(lazy.Get(func() int { return 42 }))
// the second invocation will not call init again, using the first value
fmt.Println(lazy.Get(func() int { return 43 }))
// Set can be used to set a specific value
lazy.Set(0)
fmt.Println(lazy.Get(func() int { panic("never called") }))
// Output: 42
// 42
// 0
}
func ExampleLazy_nil() {
var lazy Lazy[int]
// passing nil as the initialization function causes the zero value to be set
fmt.Println(lazy.Get(nil))
// Output: 0
}

View file

@ -1,189 +0,0 @@
package lazy
import (
"reflect"
"github.com/tkw1536/pkglib/reflectx"
)
// Pool represents a pool of laziliy initialized and potentially referencing Component instances.
//
// Component must be an interface type, that should be implemented by various pointers to structs.
// Components may reference each other, even circularly.
//
// Each type of struct is considered a singleton an initialized only once.
//
// See [Pool.All], [ExportComponents] and [ExportComponent].
//
// The zero value is ready to use.
type Pool[Component any, InitParams any] struct {
// Init is called on every component to be initialized.
Init func(Component, InitParams) Component
// Analytics are written on the first retrieval operation on this Pool.
//
// Contains all groups and structs that are referenced during initialization.
// To add extra groups, call RegisterPoolGroup.
Analytics PoolAnalytics
extraGroups []reflect.Type
all Lazy[[]Component]
}
// RegisterPoolGroup registers the given group type to be added to the pools' analytics.
//
// Only groups not referenced during initialization need to be registered explicitly.
func RegisterPoolGroup[Group any, Component any, InitParams any](p *Pool[Component, InitParams]) {
p.extraGroups = append(p.extraGroups, reflectx.TypeOf[Group]())
}
// All initializes or returns all components stored in this pool.
//
// The All function should return an array of calls to [Make] with the provided context.
// Multiple calls to the this method return the same return value.
func (p *Pool[Component, InitParams]) All(Params InitParams, All func(context *PoolContext[Component]) []Component) []Component {
return p.all.Get(func() []Component {
// create a new context
context := &PoolContext[Component]{
all: All,
init: func(c Component) Component {
if p.Init == nil {
return c
}
return p.Init(c, Params)
},
metaCache: make(map[reflect.Type]meta[Component]),
cache: make(map[string]Component),
}
// and process them all
all := context.all(context)
context.process(all)
// write out analytics
context.anal(&p.Analytics, p.extraGroups)
return all
})
}
// PoolContext is a context used during [Make].
type PoolContext[Component any] struct {
init func(Component) Component // initializes a new component
all func(context *PoolContext[Component]) []Component // initializes all components
metaCache map[reflect.Type]meta[Component]
cache map[string]Component // cached components
queue []delayedInit[Component] // init queue
}
type delayedInit[Component any] struct {
meta meta[Component]
value reflect.Value
}
// Process processes all components in the queue
func (p *PoolContext[Component]) process(all []Component) {
index := 0
for len(p.queue) > index {
p.queue[index].Run(all)
index++
}
p.queue = nil
}
func (di *delayedInit[Component]) Run(all []Component) {
di.meta.InitComponent(di.value, all)
}
// Make creates or returns a cached component of the given Context.
//
// Components are initialized by first
// Then all component-like fields of fields are filled with their appropriate components.
//
// A component-like field has one of the following types:
//
// - A pointer to a struct type that implements component
// - A slice type of an interface type that implements component
//
// Such component-like fields are only initialized if one of the following conditions are met:
//
// - The field has a tag 'auto' with the value `true`
// - The field lives inside a struct field named `Dependencies`
//
// These fields are initialized in an undefined order during initialization.
// The init function may not rely on these existing.
// Furthermore, the init function may not cause other components to be initialized.
//
// The init function may be nil, indicating that no additional initialization is required.
func Make[Component any, ConcreteComponent any](context *PoolContext[Component], init func(component ConcreteComponent)) ConcreteComponent {
// get a description of the type
cd := getMeta[Component, ConcreteComponent](context.metaCache)
// if an instance already exists, return it!
if instance, ok := context.cache[cd.Name]; ok {
return any(instance).(ConcreteComponent)
}
// create a fresh new instance and store it in the cache
context.cache[cd.Name] = context.init(cd.New())
instance := any(context.cache[cd.Name]).(ConcreteComponent)
// call the passed init function
if init != nil {
init(instance)
}
// and queue it up
if cd.NeedsInitComponent() {
context.queue = append(context.queue, delayedInit[Component]{
meta: cd,
value: reflect.ValueOf(instance),
})
}
return instance
}
//
// PUBLIC FUNCTIONS
//
// ExportComponents exports all components that are a ConcreteComponentType from the pool.
//
// All should be the function of the core that initializes all components.
// All should only make calls to [InitComponent].
func ExportComponents[Component any, InitParams any, ConcreteComponentType any](
p *Pool[Component, InitParams],
Params InitParams,
All func(context *PoolContext[Component]) []Component,
) []ConcreteComponentType {
components := p.All(Params, All)
results := make([]ConcreteComponentType, 0, len(components))
for _, comp := range components {
if cc, ok := any(comp).(ConcreteComponentType); ok {
results = append(results, cc)
}
}
return results
}
// ExportComponent exports the first component that is a ConcreteComponent from the pool.
//
// All should be the function of the core that initializes all components.
// All should only make calls to [InitComponent].
func ExportComponent[Component any, InitParams any, ConcreteComponentType any](
pool *Pool[Component, InitParams],
Params InitParams,
All func(context *PoolContext[Component]) []Component,
) ConcreteComponentType {
components := pool.All(Params, All)
for _, comp := range components {
if cc, ok := any(comp).(ConcreteComponentType); ok {
return cc
}
}
panic("ExportComponent: Attempted to export unregistered component")
}

View file

@ -1,112 +0,0 @@
package lazy
import (
"reflect"
"github.com/tkw1536/pkglib/collection"
"golang.org/x/exp/slices"
)
type PoolAnalytics struct {
Components map[string]*PoolAnalyticsComponent
Groups map[string]*PoolAnalyticsGroup
}
type PoolAnalyticsComponent struct {
Type string // Type name
Groups []string // groups this is contained in
CFields map[string]string // fields with type C for which C implements component
IFields map[string]string // fields []I where I is an interface that implements component
DCFields map[string]string // fields of the auto field with type C for which C implements component
DIFields map[string]string // fields of the auto field []I where I is an interface that implements component
Methods map[string]string // Method signatures of type
}
type PoolAnalyticsGroup struct {
Type string // Type name
Components []string // Components of this Type
Methods map[string]string // Method signatures of this interface
}
// anal writes analytics about this context to anal
func (context *PoolContext[Component]) anal(anal *PoolAnalytics, groups []reflect.Type) {
anal.Components = make(map[string]*PoolAnalyticsComponent, len(context.metaCache))
anal.Groups = make(map[string]*PoolAnalyticsGroup)
// collect all the pointers, and setup the anal.Components map!
tpPointers := make([]reflect.Type, 0, len(context.metaCache))
for _, meta := range context.metaCache {
tp := reflect.PointerTo(meta.Elem)
tpPointers = append(tpPointers, tp)
mcount := tp.NumMethod()
anal.Components[meta.Name] = &PoolAnalyticsComponent{
Groups: make([]string, 0),
Methods: make(map[string]string, mcount),
}
for i := 0; i < mcount; i++ {
method := tp.Method(i)
anal.Components[meta.Name].Methods[method.Name] = method.Type.String()
}
}
// collect interfaces to analyze
ifaces := make([]reflect.Type, len(groups))
copy(ifaces, groups)
// take all of the components out of the cache
for _, meta := range context.metaCache {
anal.Components[meta.Name].Type = meta.Name
anal.Components[meta.Name].CFields = collection.MapValues(meta.CFields, func(key string, tp reflect.Type) string {
return nameOf(tp.Elem())
})
anal.Components[meta.Name].DCFields = collection.MapValues(meta.DCFields, func(key string, tp reflect.Type) string {
return nameOf(tp.Elem())
})
anal.Components[meta.Name].IFields = collection.MapValues(meta.IFields, func(key string, iface reflect.Type) string {
ifaces = append(ifaces, iface)
return nameOf(iface)
})
anal.Components[meta.Name].DIFields = collection.MapValues(meta.DIFields, func(key string, iface reflect.Type) string {
ifaces = append(ifaces, iface)
return nameOf(iface)
})
}
// and analyze all ifaces
for _, iface := range ifaces {
name := nameOf(iface)
if _, ok := anal.Groups[name]; ok {
continue
}
types := collection.FilterClone(tpPointers, func(tp reflect.Type) bool {
return tp.AssignableTo(iface)
})
anal.Groups[name] = &PoolAnalyticsGroup{
Type: name,
Components: collection.MapSlice(types, func(tp reflect.Type) string {
cname := nameOf(tp.Elem())
anal.Components[cname].Groups = append(anal.Components[cname].Groups, name)
return cname
}),
}
mcount := iface.NumMethod()
anal.Groups[name].Methods = make(map[string]string, mcount)
for i := 0; i < mcount; i++ {
method := iface.Method(i)
anal.Groups[name].Methods[method.Name] = method.Type.String()
}
}
for _, comp := range anal.Components {
slices.Sort(comp.Groups)
}
}

View file

@ -1,177 +0,0 @@
package lazy
import (
"reflect"
"github.com/tkw1536/pkglib/collection"
"github.com/tkw1536/pkglib/reflectx"
)
// getMeta gets the component belonging to a component type
func getMeta[Component any, ConcreteComponent any](cache map[reflect.Type]meta[Component]) meta[Component] {
tp := reflectx.TypeOf[ConcreteComponent]()
// we already have a m => return it
if m, ok := cache[tp]; ok {
return m
}
// create a new m
var m meta[Component]
m.init(tp)
// store it in the cache
cache[tp] = m
return m
}
// meta stores meta-information about a specific component
type meta[Component any] struct {
Name string // the type name of this component
Elem reflect.Type // the element type of the component
CFields map[string]reflect.Type // fields with type C for which C implements component
IFields map[string]reflect.Type // fields []I where I is an interface that implements component
DCFields map[string]reflect.Type // fields with type C for which C inside auto field which implement component
DIFields map[string]reflect.Type // fields []I where I is an interface inside auto field that implements component
}
// init initializes this meta
func (m *meta[Component]) init(tp reflect.Type) {
var component = reflectx.TypeOf[Component]()
if tp.Kind() != reflect.Pointer && tp.Elem().Kind() != reflect.Struct {
panic("GetMeta: Type (" + tp.String() + ") must be backed by a pointer to slice")
}
m.Elem = tp.Elem()
m.Name = nameOf(m.Elem)
m.CFields = make(map[string]reflect.Type)
m.IFields = make(map[string]reflect.Type)
scanForFields(component, m.Name, m.Elem, false, m.CFields, m.IFields)
// check if we have a dependencies field of struct type
dependenciesField, ok := m.Elem.FieldByName(dependencies)
if !ok {
return
}
if dependenciesField.Type.Kind() != reflect.Struct {
panic("GetMeta: " + dependencies + " field (" + m.Name + ") is not a struct")
}
// and initialize the type map of the given map
m.DCFields = make(map[string]reflect.Type)
m.DIFields = make(map[string]reflect.Type)
scanForFields(component, m.Name, dependenciesField.Type, true, m.DCFields, m.DIFields)
}
// scanForFields scans the structtype for fields of component-like fields.
// they are then writen to the cFields and iFields maps.
// inDependenciesStruct indicates if we are inside a dependency struct
func scanForFields(component reflect.Type, elem string, structType reflect.Type, inDependenciesStruct bool, cFields map[string]reflect.Type, iFields map[string]reflect.Type) {
count := structType.NumField()
for i := 0; i < count; i++ {
field := structType.Field(i)
if !inDependenciesStruct && field.Tag.Get("auto") != "true" {
continue
}
if inDependenciesStruct && field.Tag != "" {
panic("GetMeta: " + dependencies + " field (" + elem + ") contains field (" + field.Name + ") with tag")
}
tp := field.Type
name := field.Name
switch {
case implementsComponent(component, tp):
cFields[name] = tp
case implementsSlice(component, tp):
iFields[name] = tp.Elem()
case inDependenciesStruct:
panic("GetMeta: " + dependencies + " field (" + elem + ") contains non-auto fields")
}
}
}
func implementsComponent(component reflect.Type, tp reflect.Type) bool {
return tp.Implements(component) && tp.Kind() == reflect.Pointer && tp.Elem().Kind() == reflect.Struct
}
func implementsSlice(component reflect.Type, tp reflect.Type) bool {
return tp.Kind() == reflect.Slice && tp.Elem().Kind() == reflect.Interface && tp.Elem().Implements(component)
}
func nameOf(tp reflect.Type) string {
return tp.PkgPath() + "." + tp.Name()
}
// New creates a new ComponentDescription
func (m meta[Component]) New() Component {
return reflect.New(m.Elem).Interface().(Component)
}
// NeedsInitComponent
func (m meta[Component]) NeedsInitComponent() bool {
return len(m.CFields) > 0 || len(m.IFields) > 0 || len(m.DCFields) > 0 || len(m.DIFields) > 0
}
// name of the dependencies field
const dependencies = "Dependencies"
// InitComponent sets up the fields of the given instance of a component.
func (m meta[Component]) InitComponent(instance reflect.Value, all []Component) {
elem := instance.Elem()
dependenciesElem := elem.FieldByName(dependencies)
// assign the component fields
for field, eType := range m.CFields {
c := collection.First(all, func(c Component) bool {
return reflect.TypeOf(c).AssignableTo(eType)
})
field := elem.FieldByName(field)
field.Set(reflect.ValueOf(c))
}
for field, eType := range m.DCFields {
c := collection.First(all, func(c Component) bool {
return reflect.TypeOf(c).AssignableTo(eType)
})
field := dependenciesElem.FieldByName(field)
field.Set(reflect.ValueOf(c))
}
// assign the interface subtypes
registryR := reflect.ValueOf(all)
for field, eType := range m.IFields {
cs := filterSubtype(registryR, eType)
field := elem.FieldByName(field)
field.Set(cs)
}
for field, eType := range m.DIFields {
cs := filterSubtype(registryR, eType)
field := dependenciesElem.FieldByName(field)
field.Set(cs)
}
}
// filterSubtype filters the slice of type []S into a slice of type []iface.
// S and I must be interface types.
func filterSubtype(sliceS reflect.Value, iface reflect.Type) reflect.Value {
len := sliceS.Len()
// convert each element
result := reflect.MakeSlice(reflect.SliceOf(iface), 0, len)
for i := 0; i < len; i++ {
element := sliceS.Index(i)
if element.Elem().Type().Implements(iface) {
result = reflect.Append(result, element.Elem().Convert(iface))
}
}
return result
}