internal/component: Move Pool into lazy package
This commit is contained in:
parent
bcfd0765b0
commit
7c3c84e116
8 changed files with 261 additions and 139 deletions
170
pkg/lazy/pool.go
Normal file
170
pkg/lazy/pool.go
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
package lazy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
)
|
||||
|
||||
// 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
|
||||
|
||||
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)
|
||||
},
|
||||
cache: make(map[string]Component),
|
||||
}
|
||||
|
||||
// and process them all
|
||||
all := context.all(context)
|
||||
context.Process(all)
|
||||
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 sync.Map
|
||||
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
|
||||
}
|
||||
}
|
||||
|
||||
var component ConcreteComponentType
|
||||
return component
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue