Files
StarAsh042 dcb98bc58b chore(release): 5.1.0
* feat(cache): 节点缓存键纳入 CSV 文件签名

ComfyUI 默认只按输入参数判断缓存是否命中。两个节点读的都是外部 CSV,
参数不变而文件被替换时缓存会一直返回旧结果,用户换了数据却看不到变化。

把 CSV 的「路径 + 修改时间 + 大小」作为 IS_CHANGED 的返回值纳入缓存键:
文件一变就重算;文件与参数都不变时指纹稳定,缓存行为与 5.0.0 一致。
路径被防护拒绝时返回稳定哨兵而不抛异常,避免打断 ComfyUI 的缓存判断。

* perf(character): 派生索引随记录缓存,单次执行 62.6ms -> 26.7ms

角色节点每次执行都在 3.4 万条记录上重建 record_map / names / 作品筛选下标,
CSV 没变时完全是重复劳动。把三者收进 CharacterIndex,与记录共用同一套
文件签名缓存(路径 + 列 + mtime + size),CSV 一变就重建。

顺带修掉作品下拉菜单的失效问题:菜单改为按默认 CSV 的文件签名缓存,
换 CSV 后自动重建,不再需要重启 ComfyUI。

实测(本机 7 次中位数):generate_characters 62.62ms -> 26.69ms(-57%)。
4 个 seed 下 prompt / characters_json / tested_count 逐字节一致。
剩余约 24ms 在 select_artists 的排除过滤里;改动它会改变 RNG 消耗顺序、
破坏同 seed 的可复现性,故本次不动。

* docs(db): 修正 generation_lock 的持锁范围说明

原文写「调用方应在进入本上下文之前完成抽样、权重计算、提示词拼装等纯 CPU 工作」,
但 RollingArtist 的重试循环必须待在事务里(命中已测组合要重新抽样),
实际持锁时长等于一次完整生成。文档与实现互相矛盾,读代码的人会误判并发行为。

改为如实描述:持锁时长由调用方决定,两种调用方式各自的代价是什么,
以及为什么不能简单把抽样挪到锁外(冲突只会推迟到写入时才被 INSERT OR IGNORE 吞掉)。
同时修正 __init__ 里「不覆盖事务内的业务计算」这句同样有歧义的注释。

纯注释改动,无代码行为变化。

* docs: 5.1.0 变更记录与 README 缓存/性能说明(T-04)

* docs: 归档 v5.1.0 迭代计划至 docs/iteration

* chore(release): 5.1.0
2026-10-25 15:57:40 +08:00

156 lines
3.8 KiB
Python

"""RollingArtist / RollingCharacter 核心实现(不依赖 ComfyUI,可独立导入与测试)。
模块划分:
- ``constants``: 路径、权重步长、规模阈值、记录类型标记与日志器
- ``params``: 节点入参的容错转换(两个节点共用)
- ``artists``: 艺术家 CSV 解析 / 加载缓存 / 索引化抽样
- ``characters``: 角色数据解析(trigger 拆解)、作品筛选、外观标签合并
- ``weights``: 权重分配、提示词构建解析、去重键
- ``db``: SQLite 记录库(已测记录)
- ``storage``: 旧版 CSV 读取与备份(供导入与留底使用)
- ``startup``: 启动期清理默认运行期文件(由 prestartup_script.py 调用)
"""
from .artists import (
ArtistRepository,
available_names,
clear_repositories,
compute_top_count,
get_artist_repository,
get_repository,
normalize_column_spec,
parse_artist_rows,
resolve_layout,
select_artists,
split_names,
)
from .characters import (
COPYRIGHT_ANY,
CharacterIndex,
CharacterRecord,
build_character_prompt,
build_payload,
build_records,
clear_records_cache,
copyright_options,
filter_by_copyright,
load_index,
load_records,
parse_copyright_filter,
split_core_tags,
split_tags,
unique_tags,
)
from .constants import (
DEFAULT_ARTIST_CSV,
DEFAULT_CHARACTER_CSV,
DEFAULT_CHARACTER_DB,
DEFAULT_DB,
DEFAULT_TESTED_CSV,
KIND_ARTIST,
KIND_CHARACTER,
LEGACY_BACKUP_SUFFIX,
LOGGER,
NODE_DIR,
WEIGHT_STEP,
)
from .db import RollingArtistDB, close_all_dbs, get_db, resolve_db_path
from .params import as_bool, as_float, as_int, as_text
from .paths import (
PathNotAllowedError,
check_csv_path,
check_db_path,
check_path,
forbidden_roots,
is_path_guard_disabled,
)
from .startup import cleanup_default_runtime_files, default_runtime_files
from .storage import backup_file, file_fingerprint, parse_tested_row, read_rows
from .weights import (
DEDUP_MODES,
WEIGHT_CURVES,
build_prompt,
dedup_key,
escape_key_part,
generate_weights,
normalize_dedup_mode,
normalize_weight_curve,
parse_prompt,
)
__all__ = [
# artists
"ArtistRepository",
"available_names",
"clear_repositories",
"compute_top_count",
"get_artist_repository",
"get_repository",
"normalize_column_spec",
"parse_artist_rows",
"resolve_layout",
"select_artists",
"split_names",
# characters
"COPYRIGHT_ANY",
"CharacterIndex",
"CharacterRecord",
"build_character_prompt",
"build_payload",
"build_records",
"clear_records_cache",
"copyright_options",
"filter_by_copyright",
"load_index",
"load_records",
"parse_copyright_filter",
"split_core_tags",
"split_tags",
"unique_tags",
# constants
"DEFAULT_ARTIST_CSV",
"DEFAULT_CHARACTER_CSV",
"DEFAULT_CHARACTER_DB",
"DEFAULT_DB",
"DEFAULT_TESTED_CSV",
"KIND_ARTIST",
"KIND_CHARACTER",
"LEGACY_BACKUP_SUFFIX",
"LOGGER",
"NODE_DIR",
"WEIGHT_STEP",
# db
"RollingArtistDB",
"close_all_dbs",
"get_db",
"resolve_db_path",
# params / startup
"as_bool",
"as_float",
"as_int",
"as_text",
"cleanup_default_runtime_files",
"default_runtime_files",
# paths
"PathNotAllowedError",
"check_csv_path",
"check_db_path",
"check_path",
"forbidden_roots",
"is_path_guard_disabled",
"backup_file",
"file_fingerprint",
"parse_tested_row",
"read_rows",
# weights
"DEDUP_MODES",
"WEIGHT_CURVES",
"build_prompt",
"dedup_key",
"escape_key_part",
"generate_weights",
"normalize_dedup_mode",
"normalize_weight_curve",
"parse_prompt",
]