package module import ( "fmt" "sort" "sync" "god-eye/internal/eventbus" ) // Registry stores modules keyed by name. Modules register themselves via // init() functions by calling Register on the default registry. type Registry struct { mu sync.RWMutex modules map[string]Module order []string // insertion order for deterministic iteration } // NewRegistry returns an empty registry. Most callers should use Default() // which returns the process-wide registry that init() functions populate. func NewRegistry() *Registry { return &Registry{modules: make(map[string]Module)} } var ( defaultRegistry *Registry defaultOnce sync.Once ) // Default returns the process-wide module registry. func Default() *Registry { defaultOnce.Do(func() { defaultRegistry = NewRegistry() }) return defaultRegistry } // Register adds m to r. Panics on duplicate name — registration happens at // init() time, so duplicates indicate a compile-time bug that must surface // immediately rather than silently overwrite. func (r *Registry) Register(m Module) { if m == nil { panic("module.Register: nil module") } name := m.Name() if name == "" { panic("module.Register: module has empty Name()") } r.mu.Lock() defer r.mu.Unlock() if _, exists := r.modules[name]; exists { panic(fmt.Sprintf("module.Register: duplicate module %q", name)) } r.modules[name] = m r.order = append(r.order, name) } // Register is a shortcut for Default().Register(m). Intended use: // // func init() { module.Register(&myModule{}) } func Register(m Module) { Default().Register(m) } // Get returns the module with the given name. func (r *Registry) Get(name string) (Module, bool) { r.mu.RLock() defer r.mu.RUnlock() m, ok := r.modules[name] return m, ok } // Names returns all registered module names in insertion order. func (r *Registry) Names() []string { r.mu.RLock() defer r.mu.RUnlock() out := make([]string, len(r.order)) copy(out, r.order) return out } // All returns every registered module in insertion order. The returned slice // is safe for the caller to iterate but do not mutate it. func (r *Registry) All() []Module { r.mu.RLock() defer r.mu.RUnlock() out := make([]Module, 0, len(r.order)) for _, n := range r.order { out = append(out, r.modules[n]) } return out } // ByPhase returns modules belonging to the given phase, sorted by name for // stable presentation. func (r *Registry) ByPhase(p Phase) []Module { r.mu.RLock() defer r.mu.RUnlock() var out []Module for _, n := range r.order { m := r.modules[n] if m.Phase() == p { out = append(out, m) } } sort.SliceStable(out, func(i, j int) bool { return out[i].Name() < out[j].Name() }) return out } // Select returns the subset of modules that should run for the given config. // A module is selected when cfg.ModuleEnabled(name) returns true (explicit // enable wins), OR when cfg leaves it unset and DefaultEnabled() is true. func (r *Registry) Select(cfg ConfigView) []Module { r.mu.RLock() defer r.mu.RUnlock() var out []Module for _, n := range r.order { m := r.modules[n] if cfg != nil { // explicit config: respect it directly if cfg.ModuleEnabled(m.Name()) { out = append(out, m) continue } // if the config has a non-default opinion (enabled=false), honor it // — but ModuleEnabled returning false could also mean "unset". // We resolve the ambiguity by checking whether any profile/CLI flag // set it via a separate mechanism; for now, fall back to the // module's default. if m.DefaultEnabled() { out = append(out, m) } continue } // no config: honor module default if m.DefaultEnabled() { out = append(out, m) } } return out } // ProducersOf returns the modules that declare t in their Produces() set. // Used by tooling and tests to validate the event-graph integrity. func (r *Registry) ProducersOf(t eventbus.EventType) []Module { r.mu.RLock() defer r.mu.RUnlock() var out []Module for _, n := range r.order { m := r.modules[n] for _, et := range m.Produces() { if et == t { out = append(out, m) break } } } return out } // ConsumersOf returns modules that declare t in their Consumes() set. func (r *Registry) ConsumersOf(t eventbus.EventType) []Module { r.mu.RLock() defer r.mu.RUnlock() var out []Module for _, n := range r.order { m := r.modules[n] for _, et := range m.Consumes() { if et == t { out = append(out, m) break } } } return out } // Reset clears the registry. Intended for tests only; never call in production // code. func (r *Registry) Reset() { r.mu.Lock() defer r.mu.Unlock() r.modules = make(map[string]Module) r.order = nil }