feat(persona): 人格系统(YAML loader / 角色人格注册+记忆隔离 / 情绪状态机+好感度)
This commit is contained in:
1 parent
653c2489bf
commit
70fd915242
11 files changed
+1568
No files matched your search
@@ -0,0 +1,186 @@
|
||||
// Package registry implements the character library and persona registration
|
||||
// for the persona system. A character comes from the character library, binds
|
||||
// exactly one persona, and declares the behavior modes it is good at; the
|
||||
// registered persona additionally owns a per-persona memory isolation namespace
|
||||
// and the emotion/affection state that follows the persona.
|
||||
package registry
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"sync"
|
||||
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/persona/emotion"
|
||||
"git.yeij.top/AskaEth/Cyrene_v2/internal/persona/loader"
|
||||
)
|
||||
|
||||
// Character is an entity from the character library. It binds one persona and
|
||||
// declares the behavior modes it excels at (as a suggestion, never an ability
|
||||
// bound — see DevDocs/design/01).
|
||||
type Character struct {
|
||||
// ID is the unique character identifier.
|
||||
ID string
|
||||
// Name is the display name of the character.
|
||||
Name string
|
||||
// Default marks this character as the library's default role.
|
||||
Default bool
|
||||
// PersonaID is the persona bound to this character.
|
||||
PersonaID string
|
||||
// Modes lists the behavior modes the character is good at.
|
||||
Modes []string
|
||||
}
|
||||
|
||||
// Registry is the in-memory, concurrency-safe character library and persona
|
||||
// registry. It also owns the emotion Manager so that emotion/affection always
|
||||
// follow the registered persona.
|
||||
type Registry struct {
|
||||
mu sync.RWMutex
|
||||
personas map[string]*loader.Persona
|
||||
characters map[string]*Character
|
||||
defaultID string
|
||||
emotions *emotion.Manager
|
||||
}
|
||||
|
||||
// New returns an empty Registry.
|
||||
func New() *Registry {
|
||||
return &Registry{
|
||||
personas: make(map[string]*loader.Persona),
|
||||
characters: make(map[string]*Character),
|
||||
emotions: emotion.NewManager(),
|
||||
}
|
||||
}
|
||||
|
||||
// RegisterPersona registers p and builds its emotion machine and affection
|
||||
// tracker. It fails when p is nil, has no id, is already registered, or its
|
||||
// emotion/affection config is invalid.
|
||||
func (r *Registry) RegisterPersona(p *loader.Persona) error {
|
||||
if p == nil {
|
||||
return fmt.Errorf("registry: nil persona")
|
||||
}
|
||||
if p.ID == "" {
|
||||
return fmt.Errorf("registry: persona id required")
|
||||
}
|
||||
machine, err := p.NewMachine()
|
||||
if err != nil {
|
||||
return fmt.Errorf("registry: persona %q machine: %w", p.ID, err)
|
||||
}
|
||||
affection, err := p.NewAffection()
|
||||
if err != nil {
|
||||
return fmt.Errorf("registry: persona %q affection: %w", p.ID, err)
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
if _, ok := r.personas[p.ID]; ok {
|
||||
r.mu.Unlock()
|
||||
return fmt.Errorf("registry: persona %q already registered", p.ID)
|
||||
}
|
||||
r.personas[p.ID] = p
|
||||
r.mu.Unlock()
|
||||
|
||||
r.emotions.Set(p.ID, machine, affection)
|
||||
return nil
|
||||
}
|
||||
|
||||
// Persona returns the persona registered under id.
|
||||
func (r *Registry) Persona(id string) (*loader.Persona, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
p, ok := r.personas[id]
|
||||
return p, ok
|
||||
}
|
||||
|
||||
// RegisterCharacter registers c. It requires c's persona to exist, so persona
|
||||
// registration must happen first. A character marked Default becomes the
|
||||
// library's default role.
|
||||
func (r *Registry) RegisterCharacter(c *Character) error {
|
||||
if c == nil {
|
||||
return fmt.Errorf("registry: nil character")
|
||||
}
|
||||
if c.ID == "" {
|
||||
return fmt.Errorf("registry: character id required")
|
||||
}
|
||||
if c.Name == "" {
|
||||
return fmt.Errorf("registry: character %q name required", c.ID)
|
||||
}
|
||||
if c.PersonaID == "" {
|
||||
return fmt.Errorf("registry: character %q personaID required", c.ID)
|
||||
}
|
||||
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
if _, ok := r.characters[c.ID]; ok {
|
||||
return fmt.Errorf("registry: character %q already registered", c.ID)
|
||||
}
|
||||
if _, ok := r.personas[c.PersonaID]; !ok {
|
||||
return fmt.Errorf("registry: character %q references unknown persona %q", c.ID, c.PersonaID)
|
||||
}
|
||||
r.characters[c.ID] = c
|
||||
if c.Default {
|
||||
r.defaultID = c.ID
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Character returns the character registered under id.
|
||||
func (r *Registry) Character(id string) (*Character, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
c, ok := r.characters[id]
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// SetDefaultCharacter makes the character with id the library default. It fails
|
||||
// when id is not a registered character.
|
||||
func (r *Registry) SetDefaultCharacter(id string) error {
|
||||
r.mu.Lock()
|
||||
defer r.mu.Unlock()
|
||||
c, ok := r.characters[id]
|
||||
if !ok {
|
||||
return fmt.Errorf("registry: set default to unknown character %q", id)
|
||||
}
|
||||
c.Default = true
|
||||
r.defaultID = id
|
||||
return nil
|
||||
}
|
||||
|
||||
// DefaultCharacter returns the library's default character, if one is set.
|
||||
func (r *Registry) DefaultCharacter() (*Character, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
if r.defaultID == "" {
|
||||
return nil, false
|
||||
}
|
||||
c, ok := r.characters[r.defaultID]
|
||||
return c, ok
|
||||
}
|
||||
|
||||
// MemoryNamespace returns the per-persona memory isolation namespace for the
|
||||
// persona under personaID, guarding memory isolation between personas.
|
||||
func (r *Registry) MemoryNamespace(personaID string) (string, bool) {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
p, ok := r.personas[personaID]
|
||||
if !ok {
|
||||
return "", false
|
||||
}
|
||||
return p.MemoryNamespace(), true
|
||||
}
|
||||
|
||||
// Emotions returns the emotion Manager that owns every registered persona's
|
||||
// emotion and affection state.
|
||||
func (r *Registry) Emotions() *emotion.Manager {
|
||||
return r.emotions
|
||||
}
|
||||
|
||||
// CountPersonas returns the number of registered personas.
|
||||
func (r *Registry) CountPersonas() int {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return len(r.personas)
|
||||
}
|
||||
|
||||
// CountCharacters returns the number of registered characters.
|
||||
func (r *Registry) CountCharacters() int {
|
||||
r.mu.RLock()
|
||||
defer r.mu.RUnlock()
|
||||
return len(r.characters)
|
||||
}
|
||||
Reference in new issue
Block a user