Files
cultivation-world-simulator/src/classes/event_manager.py
2025-11-24 22:30:49 +08:00

127 lines
5.0 KiB
Python
Raw Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
from typing import Dict, List
from collections import deque, defaultdict
from src.classes.event import Event
class EventManager:
"""
全局事件管理器:统一保存事件,并提供按角色、按角色对、按时间的查询。
- 限长清理,避免内存无限增长。
- 幂等写入(基于 event_id
- 仅对恰为两人参与的事件建立“按人对”索引。
"""
def __init__(self, *, max_global_events: int = 5000, max_index_events: int = 200) -> None:
self.max_global_events = max_global_events
self.max_index_events = max_index_events
self._events: deque[Event] = deque()
self._by_id: Dict[str, Event] = {}
self._by_avatar: Dict[str, deque[Event]] = defaultdict(deque)
self._by_pair: Dict[frozenset[str], deque[Event]] = defaultdict(deque)
# 按角色分类的大事/小事索引
self._by_avatar_major: Dict[str, deque[Event]] = defaultdict(deque)
self._by_avatar_minor: Dict[str, deque[Event]] = defaultdict(deque)
# 按角色对分类的大事/小事索引
self._by_pair_major: Dict[frozenset[str], deque[Event]] = defaultdict(deque)
self._by_pair_minor: Dict[frozenset[str], deque[Event]] = defaultdict(deque)
def _append_with_limit(self, dq: deque, item: Event) -> None:
dq.append(item)
if len(dq) > self.max_index_events:
dq.popleft()
def add_event(self, event: Event) -> None:
# 过滤掉空事件
from src.classes.event import is_null_event
if is_null_event(event):
return
# 幂等:若已存在同 id跳过
if getattr(event, "id", None) and event.id in self._by_id:
return
if getattr(event, "id", None):
self._by_id[event.id] = event
# 全局
self._events.append(event)
if len(self._events) > self.max_global_events:
self._events.popleft()
# 分索引:按人/人对
rel = event.related_avatars or []
rel_unique = list(dict.fromkeys(rel)) # 去重但保持顺序
for aid in rel_unique:
self._append_with_limit(self._by_avatar[aid], event)
# 故事事件进入小事索引,不进入大事索引
if event.is_story:
self._append_with_limit(self._by_avatar_minor[aid], event)
elif event.is_major:
self._append_with_limit(self._by_avatar_major[aid], event)
else:
self._append_with_limit(self._by_avatar_minor[aid], event)
# 仅当且仅当"恰有两位参与者"时建立按人对索引
if len(rel_unique) == 2:
a, b = rel_unique[0], rel_unique[1]
pair_key = frozenset([a, b])
self._append_with_limit(self._by_pair[pair_key], event)
# 角色对也建立分类索引
if event.is_story:
self._append_with_limit(self._by_pair_minor[pair_key], event)
elif event.is_major:
self._append_with_limit(self._by_pair_major[pair_key], event)
else:
self._append_with_limit(self._by_pair_minor[pair_key], event)
# —— 查询接口 ——
def get_recent_events(self, limit: int = 100) -> List[Event]:
if limit <= 0:
return []
return list(self._events)[-limit:]
def get_events_by_avatar(self, avatar_id: str, *, limit: int = 50) -> List[Event]:
dq = self._by_avatar.get(avatar_id)
if not dq:
return []
return list(dq)[-limit:]
def get_events_between(self, avatar_id1: str, avatar_id2: str, *, limit: int = 50) -> List[Event]:
key = frozenset([avatar_id1, avatar_id2])
dq = self._by_pair.get(key)
if not dq:
return []
return list(dq)[-limit:]
def get_major_events_by_avatar(self, avatar_id: str, *, limit: int = 10) -> List[Event]:
"""获取角色的大事(长期记忆)"""
dq = self._by_avatar_major.get(avatar_id)
if not dq:
return []
return list(dq)[-limit:]
def get_minor_events_by_avatar(self, avatar_id: str, *, limit: int = 10) -> List[Event]:
"""获取角色的小事(短期记忆)"""
dq = self._by_avatar_minor.get(avatar_id)
if not dq:
return []
return list(dq)[-limit:]
def get_major_events_between(self, avatar_id1: str, avatar_id2: str, *, limit: int = 10) -> List[Event]:
"""获取两个角色之间的大事(长期记忆)"""
key = frozenset([avatar_id1, avatar_id2])
dq = self._by_pair_major.get(key)
if not dq:
return []
return list(dq)[-limit:]
def get_minor_events_between(self, avatar_id1: str, avatar_id2: str, *, limit: int = 10) -> List[Event]:
"""获取两个角色之间的小事(短期记忆)"""
key = frozenset([avatar_id1, avatar_id2])
dq = self._by_pair_minor.get(key)
if not dq:
return []
return list(dq)[-limit:]