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
|
|
@ -1,65 +1,34 @@
|
|||
package component
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
"sync"
|
||||
|
||||
"github.com/FAU-CDI/wisski-distillery/pkg/lazy"
|
||||
)
|
||||
|
||||
// Pool holds a pool of components and provides factilities to create and access them.
|
||||
// See [Pool.All], [ExportComponents] and [ExportComponent].
|
||||
type Pool struct {
|
||||
all lazy.Lazy[[]Component] // all components
|
||||
type ComponentPool struct {
|
||||
pool lazy.Pool[Component, Core]
|
||||
poolInit sync.Once
|
||||
}
|
||||
|
||||
// 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) All(All func(context *PoolContext) []Component) []Component {
|
||||
return p.all.Get(func() []Component {
|
||||
// create a new context
|
||||
context := &PoolContext{
|
||||
all: All,
|
||||
cache: make(map[string]Component),
|
||||
func (pool *ComponentPool) init() {
|
||||
pool.poolInit.Do(func() {
|
||||
pool.pool.Init = func(component Component, core Core) Component {
|
||||
component.getBase().Core = core
|
||||
return component
|
||||
}
|
||||
|
||||
// and process them all
|
||||
all := context.all(context)
|
||||
context.process(all)
|
||||
return all
|
||||
})
|
||||
}
|
||||
|
||||
// PoolContext is a context used during [Make].
|
||||
// It should not be initialized by a user.
|
||||
type PoolContext struct {
|
||||
all func(context *PoolContext) []Component // function to return all components
|
||||
type ComponentPoolContext = *lazy.PoolContext[Component]
|
||||
type ComponentAllFunc = func(context ComponentPoolContext) []Component
|
||||
|
||||
cache map[string]Component // cached components
|
||||
queue []delayedInit // init queue
|
||||
func (pool *ComponentPool) All(core Core, init func(context ComponentPoolContext) []Component) []Component {
|
||||
pool.init()
|
||||
return pool.pool.All(core, init)
|
||||
}
|
||||
|
||||
type delayedInit struct {
|
||||
meta meta
|
||||
value reflect.Value
|
||||
}
|
||||
|
||||
func (di delayedInit) Do(all []Component) {
|
||||
di.meta.InitComponent(di.value, all)
|
||||
}
|
||||
|
||||
// process processes the queue in this process
|
||||
func (p *PoolContext) process(all []Component) {
|
||||
index := 0
|
||||
for len(p.queue) > index {
|
||||
p.queue[index].Do(all)
|
||||
index++
|
||||
}
|
||||
p.queue = nil
|
||||
}
|
||||
|
||||
// Make creates or returns a cached component of the given Context.
|
||||
// MakeComponent creates or returns a cached component of the given Context.
|
||||
//
|
||||
// Components are initialized by first calling the init function.
|
||||
// Then all component-like fields of fields are filled with their appropriate components.
|
||||
|
|
@ -74,74 +43,24 @@ func (p *PoolContext) process(all []Component) {
|
|||
// 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[C Component](context *PoolContext, core Core, init func(component C)) C {
|
||||
// get a description of the type
|
||||
cd := getMeta[C]()
|
||||
|
||||
// if an instance already exists, return it!
|
||||
if instance, ok := context.cache[cd.Name]; ok {
|
||||
return instance.(C)
|
||||
}
|
||||
|
||||
// make sure that we have an array of components
|
||||
if context.cache == nil {
|
||||
context.cache = make(map[string]Component)
|
||||
}
|
||||
|
||||
// create a fresh (empty) instance
|
||||
context.cache[cd.Name] = cd.New()
|
||||
instance := context.cache[cd.Name].(C)
|
||||
|
||||
// do the core and self-initialization
|
||||
instance.getBase().Core = core
|
||||
|
||||
if init != nil {
|
||||
init(instance)
|
||||
}
|
||||
|
||||
if cd.NeedsInitComponent() {
|
||||
context.queue = append(context.queue, delayedInit{
|
||||
meta: cd,
|
||||
value: reflect.ValueOf(instance),
|
||||
})
|
||||
}
|
||||
|
||||
return instance
|
||||
func MakeComponent[C Component](context ComponentPoolContext, core Core, init func(component C)) C {
|
||||
return lazy.Make(context, init)
|
||||
}
|
||||
|
||||
//
|
||||
// PUBLIC FUNCTIONS
|
||||
//
|
||||
|
||||
// ExportComponents exports all components that are a C from the pool.
|
||||
//
|
||||
// All should be the function of the core that initializes all components.
|
||||
// All should only make calls to [InitComponent].
|
||||
func ExportComponents[C Component](p *Pool, All func(context *PoolContext) []Component) []C {
|
||||
components := p.All(All)
|
||||
|
||||
results := make([]C, 0, len(components))
|
||||
for _, comp := range components {
|
||||
if cc, ok := comp.(C); ok {
|
||||
results = append(results, cc)
|
||||
}
|
||||
}
|
||||
return results
|
||||
func ExportComponents[C Component](pool *ComponentPool, core Core, All ComponentAllFunc) []C {
|
||||
pool.init()
|
||||
return lazy.ExportComponents[Component, Core, C](&pool.pool, core, All)
|
||||
}
|
||||
|
||||
// ExportComponent exports the first component that is a C from the pool.
|
||||
//
|
||||
// All should be the function of the core that initializes all components.
|
||||
// All should only make calls to [InitComponent].
|
||||
func ExportComponent[C Component](p *Pool, All func(context *PoolContext) []Component) C {
|
||||
components := p.All(All)
|
||||
|
||||
for _, comp := range components {
|
||||
if cc, ok := comp.(C); ok {
|
||||
return cc
|
||||
}
|
||||
}
|
||||
|
||||
var c C
|
||||
return c
|
||||
func ExportComponent[C Component](pool *ComponentPool, core Core, All ComponentAllFunc) C {
|
||||
pool.init()
|
||||
return lazy.ExportComponent[Component, Core, C](&pool.pool, core, All)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ import (
|
|||
)
|
||||
|
||||
// register returns all components of the distillery
|
||||
func (dis *Distillery) register(context *component.PoolContext) []component.Component {
|
||||
func (dis *Distillery) register(context component.ComponentPoolContext) []component.Component {
|
||||
return []component.Component{
|
||||
ra[*web.Web](dis, context),
|
||||
|
||||
|
|
@ -58,11 +58,11 @@ func (dis *Distillery) register(context *component.PoolContext) []component.Comp
|
|||
}
|
||||
|
||||
// r initializes a component from the provided distillery.
|
||||
func r[C component.Component](dis *Distillery, context *component.PoolContext, init func(component C)) C {
|
||||
return component.Make(context, dis.Core, init)
|
||||
func r[C component.Component](dis *Distillery, context component.ComponentPoolContext, init func(component C)) C {
|
||||
return component.MakeComponent(context, dis.Core, init)
|
||||
}
|
||||
|
||||
// ra is like r, but does not provided additional initialization
|
||||
func ra[C component.Component](dis *Distillery, context *component.PoolContext) C {
|
||||
func ra[C component.Component](dis *Distillery, context component.ComponentPoolContext) C {
|
||||
return r[C](dis, context, nil)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -32,7 +32,7 @@ type Distillery struct {
|
|||
Upstream Upstream
|
||||
|
||||
// Pool holds all the components in this pool
|
||||
pool component.Pool
|
||||
pool component.ComponentPool
|
||||
}
|
||||
|
||||
// Upstream contains the configuration for accessing remote configuration.
|
||||
|
|
@ -54,6 +54,7 @@ func (dis *Distillery) Context() context.Context {
|
|||
func e[C component.Component](dis *Distillery) C {
|
||||
return component.ExportComponent[C](
|
||||
&dis.pool,
|
||||
dis.Core,
|
||||
dis.register,
|
||||
)
|
||||
}
|
||||
|
|
@ -61,6 +62,7 @@ func e[C component.Component](dis *Distillery) C {
|
|||
func ea[C component.Component](dis *Distillery) []C {
|
||||
return component.ExportComponents[C](
|
||||
&dis.pool,
|
||||
dis.Core,
|
||||
dis.register,
|
||||
)
|
||||
}
|
||||
|
|
|
|||
|
|
@ -1,11 +1,8 @@
|
|||
package errorx
|
||||
|
||||
import "github.com/tkw1536/goprogram/lib/collection"
|
||||
|
||||
// First returns the first non-nil error, or nil otherwise.
|
||||
func First(errors ...error) error {
|
||||
for _, err := range errors {
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
return collection.First(errors, func(err error) bool { return err != nil })
|
||||
}
|
||||
|
|
|
|||
|
|
@ -4,20 +4,20 @@ import (
|
|||
"sync"
|
||||
)
|
||||
|
||||
// Lazy is an object that a lazily-initialized value of type T.
|
||||
//
|
||||
// A Lazy must not be copied after first use.
|
||||
// Lazy holds a lazily initialized value of T.
|
||||
// Lazy 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
|
||||
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, initializes the value using the init function.
|
||||
// Otherwise, it returns the initialized value.
|
||||
// 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.
|
||||
|
|
@ -28,13 +28,19 @@ func (lazy *Lazy[T]) Get(init func() T) T {
|
|||
defer lazy.m.RUnlock()
|
||||
|
||||
lazy.once.Do(func() {
|
||||
lazy.value = init()
|
||||
if init != nil {
|
||||
lazy.value = init()
|
||||
}
|
||||
})
|
||||
|
||||
return lazy.value
|
||||
}
|
||||
|
||||
// Set atomically sets the value of this lazy, preventing future calls to get from invoking init.
|
||||
// It may be called concurrently with calls to [Get] and [Reset].
|
||||
// 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()
|
||||
|
|
|
|||
31
pkg/lazy/lazy_test.go
Normal file
31
pkg/lazy/lazy_test.go
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
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
|
||||
}
|
||||
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
|
||||
}
|
||||
|
|
@ -1,4 +1,4 @@
|
|||
package component
|
||||
package lazy
|
||||
|
||||
import (
|
||||
"reflect"
|
||||
|
|
@ -8,19 +8,17 @@ import (
|
|||
"github.com/tkw1536/goprogram/lib/reflectx"
|
||||
)
|
||||
|
||||
var metaCache sync.Map // Map[reflect.Type]meta
|
||||
|
||||
// getMeta gets the component belonging to a component type
|
||||
func getMeta[C Component]() meta {
|
||||
tp := reflectx.TypeOf[C]()
|
||||
func getMeta[Component any, ConcreteComponent any](metaCache *sync.Map) meta[Component] {
|
||||
tp := reflectx.TypeOf[ConcreteComponent]()
|
||||
|
||||
// we already have a m => return it
|
||||
if m, ok := metaCache.Load(tp); ok {
|
||||
return m.(meta)
|
||||
return m.(meta[Component])
|
||||
}
|
||||
|
||||
// create a new m
|
||||
var m meta
|
||||
var m meta[Component]
|
||||
m.init(tp)
|
||||
|
||||
// store it in the cache
|
||||
|
|
@ -29,7 +27,7 @@ func getMeta[C Component]() meta {
|
|||
}
|
||||
|
||||
// meta stores meta-information about a specific component
|
||||
type meta struct {
|
||||
type meta[Component any] struct {
|
||||
Name string // the type name of this component
|
||||
Elem reflect.Type // the element type of the component
|
||||
|
||||
|
|
@ -37,11 +35,10 @@ type meta struct {
|
|||
IFields map[string]reflect.Type // fields []I where I is an interface that implements component
|
||||
}
|
||||
|
||||
// componentType is the type of components
|
||||
var componentType = reflectx.TypeOf[Component]()
|
||||
// init initializes this meta
|
||||
func (m *meta[Component]) init(tp reflect.Type) {
|
||||
var componentType = reflectx.TypeOf[Component]()
|
||||
|
||||
// init initializes this refklecttype
|
||||
func (m *meta) init(tp reflect.Type) {
|
||||
if tp.Kind() != reflect.Pointer && tp.Elem().Kind() != reflect.Struct {
|
||||
panic("GetMeta: Type (" + tp.String() + ") must be backed by a pointer to slice")
|
||||
}
|
||||
|
|
@ -73,17 +70,17 @@ func (m *meta) init(tp reflect.Type) {
|
|||
}
|
||||
|
||||
// New creates a new ComponentDescription
|
||||
func (m meta) New() Component {
|
||||
func (m meta[Component]) New() Component {
|
||||
return reflect.New(m.Elem).Interface().(Component)
|
||||
}
|
||||
|
||||
// NeedsInitComponent
|
||||
func (m meta) NeedsInitComponent() bool {
|
||||
func (m meta[Component]) NeedsInitComponent() bool {
|
||||
return len(m.CFields) > 0 || len(m.IFields) > 0
|
||||
}
|
||||
|
||||
// InitComponent sets up the fields of the given instance of a component.
|
||||
func (m meta) InitComponent(instance reflect.Value, all []Component) {
|
||||
func (m meta[Component]) InitComponent(instance reflect.Value, all []Component) {
|
||||
elem := instance.Elem()
|
||||
|
||||
// assign the component fields
|
||||
Loading…
Add table
Add a link
Reference in a new issue