diff --git a/internal/dis/component/control/info/components.go b/internal/dis/component/control/info/components.go
new file mode 100644
index 0000000..a3f8900
--- /dev/null
+++ b/internal/dis/component/control/info/components.go
@@ -0,0 +1,27 @@
+package info
+
+import (
+ _ "embed"
+ "net/http"
+ "time"
+
+ "github.com/FAU-CDI/wisski-distillery/internal/dis/component/control/static"
+ "github.com/FAU-CDI/wisski-distillery/pkg/lazy"
+)
+
+//go:embed "html/components.html"
+var componentsTemplateString string
+var componentsTemplate = static.AssetsComponentsIndex.MustParse(componentsTemplateString)
+
+type componentsPageContext struct {
+ Time time.Time
+
+ Analytics lazy.PoolAnalytics
+}
+
+func (info *Info) componentsPageAPI(r *http.Request) (cp componentsPageContext, err error) {
+ cp.Analytics = *info.Analytics
+ cp.Time = time.Now().UTC()
+
+ return
+}
diff --git a/internal/dis/component/control/info/html/components.html b/internal/dis/component/control/info/html/components.html
new file mode 100644
index 0000000..a35e5f6
--- /dev/null
+++ b/internal/dis/component/control/info/html/components.html
@@ -0,0 +1,143 @@
+
+
+
+
diff --git a/internal/dis/component/control/info/info.go b/internal/dis/component/control/info/info.go
index 8903a43..19724b3 100644
--- a/internal/dis/component/control/info/info.go
+++ b/internal/dis/component/control/info/info.go
@@ -10,12 +10,15 @@ import (
"github.com/FAU-CDI/wisski-distillery/internal/dis/component/instances"
"github.com/FAU-CDI/wisski-distillery/pkg/httpx"
+ "github.com/FAU-CDI/wisski-distillery/pkg/lazy"
"github.com/tkw1536/goprogram/stream"
)
type Info struct {
component.Base
+ Analytics *lazy.PoolAnalytics
+
Exporter *exporter.Exporter
Instances *instances.Instances
SnapshotsLog *logger.Logger
@@ -41,6 +44,12 @@ func (info *Info) Handler(route string, context context.Context, io stream.IOStr
Template: indexTemplate,
})
+ // add a handler for the component page
+ mux.Handle(route+"components", httpx.HTMLHandler[componentsPageContext]{
+ Handler: info.componentsPageAPI,
+ Template: componentsTemplate,
+ })
+
// add a handler for the instance page
mux.Handle(route+"instance/", httpx.HTMLHandler[instancePageContext]{
Handler: info.instancePageAPI,
diff --git a/internal/dis/component/control/static/assets.go b/internal/dis/component/control/static/assets.go
index a48e3e8..6afe929 100644
--- a/internal/dis/component/control/static/assets.go
+++ b/internal/dis/component/control/static/assets.go
@@ -22,7 +22,7 @@ type Assets struct {
Styles string // tags inserted by the asset
}
-//go:generate node build.mjs HomeHome ControlIndex ControlInstance
+//go:generate node build.mjs HomeHome ComponentsIndex ControlIndex ControlInstance
// MustParse parses a new template from the given source
// and registers the Asset functions to it.
diff --git a/internal/dis/component/control/static/assets_dist.go b/internal/dis/component/control/static/assets_dist.go
index a1ad92d..c59144e 100644
--- a/internal/dis/component/control/static/assets_dist.go
+++ b/internal/dis/component/control/static/assets_dist.go
@@ -8,6 +8,12 @@ var AssetsHomeHome = Assets{
Styles: ``,
}
+// AssetsComponentsIndex contains assets for the 'ComponentsIndex' entrypoint.
+var AssetsComponentsIndex = Assets{
+ Scripts: ``,
+ Styles: ``,
+}
+
// AssetsControlIndex contains assets for the 'ControlIndex' entrypoint.
var AssetsControlIndex = Assets{
Scripts: ``,
diff --git a/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.css b/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.css
new file mode 100644
index 0000000..e69de29
diff --git a/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.js b/internal/dis/component/control/static/dist/ComponentsIndex.38d394c2.js
new file mode 100644
index 0000000..e69de29
diff --git a/internal/dis/component/control/static/src/entry/ComponentsIndex/index.css b/internal/dis/component/control/static/src/entry/ComponentsIndex/index.css
new file mode 100644
index 0000000..e69de29
diff --git a/internal/dis/component/control/static/src/entry/ComponentsIndex/index.ts b/internal/dis/component/control/static/src/entry/ComponentsIndex/index.ts
new file mode 100644
index 0000000..b0acb4c
--- /dev/null
+++ b/internal/dis/component/control/static/src/entry/ComponentsIndex/index.ts
@@ -0,0 +1 @@
+// nothing for now
\ No newline at end of file
diff --git a/internal/dis/distillery.go b/internal/dis/distillery.go
index a9974ea..fa4303e 100644
--- a/internal/dis/distillery.go
+++ b/internal/dis/distillery.go
@@ -137,6 +137,8 @@ func (dis *Distillery) allComponents() []initFunc {
manual(func(resolver *resolver.Resolver) {
resolver.RefreshInterval = time.Minute
}),
- auto[*info.Info],
+ manual(func(info *info.Info) {
+ info.Analytics = &dis.pool.Analytics
+ }),
}
}
diff --git a/pkg/lazy/pool.go b/pkg/lazy/pool.go
index 07943c7..d647f7a 100644
--- a/pkg/lazy/pool.go
+++ b/pkg/lazy/pool.go
@@ -2,7 +2,6 @@ package lazy
import (
"reflect"
- "sync"
)
// Pool represents a pool of laziliy initialized and potentially referencing Component instances.
@@ -19,6 +18,9 @@ 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]
}
@@ -37,12 +39,16 @@ func (p *Pool[Component, InitParams]) All(Params InitParams, All func(context *P
}
return p.Init(c, Params)
},
- cache: make(map[string]Component),
+ metaCache: make(map[reflect.Type]meta[Component]),
+ cache: make(map[string]Component),
}
// and process them all
all := context.all(context)
- context.Process(all)
+ context.process(all)
+
+ // write out analytics
+ context.anal(&p.Analytics)
return all
})
}
@@ -56,7 +62,7 @@ type PoolContext[Component any] struct {
// function to return all components
- metaCache sync.Map // Map[string]meta[Component]
+ metaCache map[reflect.Type]meta[Component]
cache map[string]Component // cached components
queue []delayedInit[Component] // init queue
}
@@ -67,7 +73,7 @@ type delayedInit[Component any] struct {
}
// Process processes all components in the queue
-func (p *PoolContext[Component]) Process(all []Component) {
+func (p *PoolContext[Component]) process(all []Component) {
index := 0
for len(p.queue) > index {
p.queue[index].Run(all)
@@ -97,7 +103,7 @@ func (di *delayedInit[Component]) Run(all []Component) {
// 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)
+ cd := getMeta[Component, ConcreteComponent](context.metaCache)
// if an instance already exists, return it!
if instance, ok := context.cache[cd.Name]; ok {
diff --git a/pkg/lazy/pool_anal.go b/pkg/lazy/pool_anal.go
new file mode 100644
index 0000000..806994d
--- /dev/null
+++ b/pkg/lazy/pool_anal.go
@@ -0,0 +1,93 @@
+package lazy
+
+import (
+ "reflect"
+
+ "github.com/tkw1536/goprogram/lib/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
+
+ 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) {
+ 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()
+ }
+ }
+
+ // 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].IFields = collection.MapValues(meta.IFields, func(key string, iface reflect.Type) string {
+ name := nameOf(iface)
+
+ if _, ok := anal.Groups[name]; !ok {
+ 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()
+ }
+ }
+
+ return name
+ })
+ }
+
+ for _, comp := range anal.Components {
+ slices.Sort(comp.Groups)
+ }
+}
diff --git a/pkg/lazy/pool_meta.go b/pkg/lazy/pool_meta.go
index 54539e7..a548c3a 100644
--- a/pkg/lazy/pool_meta.go
+++ b/pkg/lazy/pool_meta.go
@@ -2,19 +2,18 @@ package lazy
import (
"reflect"
- "sync"
"github.com/tkw1536/goprogram/lib/collection"
"github.com/tkw1536/goprogram/lib/reflectx"
)
// getMeta gets the component belonging to a component type
-func getMeta[Component any, ConcreteComponent any](metaCache *sync.Map) meta[Component] {
+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 := metaCache.Load(tp); ok {
- return m.(meta[Component])
+ if m, ok := cache[tp]; ok {
+ return m
}
// create a new m
@@ -22,7 +21,7 @@ func getMeta[Component any, ConcreteComponent any](metaCache *sync.Map) meta[Com
m.init(tp)
// store it in the cache
- metaCache.Store(tp, m)
+ cache[tp] = m
return m
}
@@ -43,8 +42,8 @@ func (m *meta[Component]) init(tp reflect.Type) {
panic("GetMeta: Type (" + tp.String() + ") must be backed by a pointer to slice")
}
- m.Name = tp.Elem().PkgPath() + "." + tp.Elem().Name()
m.Elem = tp.Elem()
+ m.Name = nameOf(m.Elem)
m.CFields = make(map[string]reflect.Type)
m.IFields = make(map[string]reflect.Type)
@@ -69,6 +68,10 @@ func (m *meta[Component]) init(tp reflect.Type) {
}
}
+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)