wisski-cloud-distillery/pkg/lazy/pool.go
2022-11-18 08:40:44 +01:00

175 lines
5.2 KiB
Go

package lazy
import (
"reflect"
)
// 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
Analytics PoolAnalytics
all Lazy[[]Component]
}
// 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)
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
// cache for metas
// function to return 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
//
// 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")
}