package emotion import ( "errors" "fmt" "sync" ) // ErrUnknownPersona is returned by Manager when an operation targets a persona // id that was never registered. var ErrUnknownPersona = errors.New("emotion: unknown persona") // entry binds a persona's machine and affection together so whatever happens to // one persona never leaks into another. type entry struct { machine *Machine affection *Affection } // Manager owns per-persona emotion and affection. Each persona id maps to its // own Machine and Affection, which is what makes emotion follow the persona // rather than a behavior mode. All methods are safe for concurrent use. type Manager struct { mu sync.RWMutex items map[string]*entry } // NewManager returns an empty Manager. func NewManager() *Manager { return &Manager{items: make(map[string]*entry)} } // Set registers (or replaces) the machine and affection for id. func (m *Manager) Set(id string, machine *Machine, affection *Affection) { m.mu.Lock() defer m.mu.Unlock() m.items[id] = &entry{machine: machine, affection: affection} } // Get returns the machine and affection for id, and whether it is registered. func (m *Manager) Get(id string) (*Machine, *Affection, bool) { m.mu.RLock() defer m.mu.RUnlock() e, ok := m.items[id] if !ok { return nil, nil, false } return e.machine, e.affection, true } // Remove deletes the entry for id, reporting whether an entry was present. func (m *Manager) Remove(id string) bool { m.mu.Lock() defer m.mu.Unlock() if _, ok := m.items[id]; !ok { return false } delete(m.items, id) return true } // Transition moves the persona identified by id into the given emotion state. func (m *Manager) Transition(id string, to EmotionState) error { machine, _, ok := m.Get(id) if !ok { return fmt.Errorf("emotion: transition for %q: %w", id, ErrUnknownPersona) } if err := machine.Transition(to); err != nil { return fmt.Errorf("emotion: transition persona %q to %q: %w", id, to, err) } return nil } // AdjustAffection applies a signed delta to the persona's affection. It returns // the before and after values and an error for unknown personas. func (m *Manager) AdjustAffection(id string, delta int) (before, after int, err error) { _, affection, ok := m.Get(id) if !ok { return 0, 0, fmt.Errorf("emotion: affection for %q: %w", id, ErrUnknownPersona) } before, after = affection.Adjust(delta) return before, after, nil } // Count reports how many personas are currently tracked. func (m *Manager) Count() int { m.mu.RLock() defer m.mu.RUnlock() return len(m.items) }