Ros

health代码注释

一、force_me 设计说明

<librose>/health.cpp
-------
const std::string thealth::health_dat_filename(bool force_me, time_t t) const
{
	const struct tm* timeptr = localtime(&t);
	if (timeptr == nullptr) {
		// if t is too lareg, for error with ms unit, timeptr maybe nullptr.
		return null_str;
	}
 
	const std::string& health_dir = force_me? me_health_dir_: curr_health_dir_;
	char buf[512];
	SDL_snprintf(buf, sizeof(buf), "%s/health%04d%02d%02d.dat", 
		health_dir.c_str(), 1900 + timeptr->tm_year, timeptr->tm_mon + 1, timeptr->tm_mday);
	return buf;
}

 

1.1 为什么引入 force_me

背景

Launcher 的做操数据(healthYYYYMMDD.dat)有两类来源:

  1. 自己的数据 —— 存储在 saves/health/ 下,由本机跟练产生。
  2. 粉丝的数据 —— 存储在 fans/<uid>/health/ 下,由粉丝从手机导出后发给 UP 主。

看板界面(图表窗口)可以在“我”和多个粉丝之间切换。为了支持这种切换,代码中引入了一个运行时状态:

std::string curr_health_dir_;     // 看板当前选中的目录
const std::string me_health_dir_; // 永远指向"我"的目录

curr_health_dir_ 会随着看板切换而改变;me_health_dir_ 则是常量,永远指向自己的目录。

问题

后台任务(例如“正在做操”)在运行时,需要始终读写自己的数据,不能受看板当前选择的影响。否则会出现:

  1. 看板正在看粉丝 A 的图表;
  2. 后台正在做操,写自己的实时数据;
  3. 如果直接使用 curr_health_dir_,数据会被错误地写到粉丝 A 的目录下。

解决方案

引入一个明确的开关 force_me,让调用者显式声明:

“这次读写必须走我自己的目录,不受看板当前选择影响。”

 

1.2 force_me 的功能

force_me 是一个 bool 参数,语义为:

是否强制使用 me_health_dir_(自己的目录),而忽略 curr_health_dir_(看板当前选中的目录)。

取值使用目录典型场景
trueme_health_dir_后台做操、实时写入自己的数据;任何“必须操作自己数据”的逻辑
falsecurr_health_dir_看板渲染当前选中的用户(可能是自己,也可能是粉丝)

关键点:

  • force_me == true 时,完全不看 curr_health_dir_,哪怕看板正在看粉丝。
  • force_me == false 时,走 curr_health_dir_,具体是谁的数据由外部状态决定。
  • force_me == false 时,curr_health_dir_ 有可能等于 me_health_dir_(看板正在看自己),也有可能是粉丝目录。

所以 force_me 表达的是“是否绕过看板选择”,而不是“数据是不是我的”。 这就是它不叫 is_me 的原因——is_me 会让人误以为它描述的是数据归属,而实际上它描述的是目录选择策略。

 

1.3 核心原则:写操作默认走 true,读操作才可能需要 false

这是使用 force_me 时最重要的一条判断准则,理解它就掌握了整个设计:

原则上,UP 主不会修改粉丝发来的 healthxxx.dat。
所有写操作,默认就是写自己的数据,因此 force_me = true。
只有读操作,才需要考虑 force_me = false(看板当前选中的用户)。

为什么这样设计?

1. 粉丝发来的 .dat 是“只读快照”

  • 它是粉丝手机上的原始记录,UP 主看的是这份快照。
  • UP 主不该、也不需要去改它。改了会让粉丝本地数据和 UP 主看到的不一致。
  • 所以“写粉丝的 .dat”不是制度性的常规行为,而是意外情况下的特殊操作。

2. 评语的写入路径是“粉丝侧”

  • UP 主在图表上写评语,评语本身是UP 主产出的一条数据。
  • 它通过私信/群发等方式发给粉丝,由粉丝自己的 App 把它融入粉丝本地的 healthxxx.dat。
  • 对粉丝的 App 来说,这是写自己的数据,所以走 force_me = true。
  • 对 UP 主的 Launcher 来说,评语只是被发出,不涉及写粉丝的 .dat。

3. 后台任务写的是自己的实时数据

  • 正在做操时,姿态点、事件、TLV 历史都往自己的目录写。
  • 这也是 force_me = true。

判断表

操作类型默认 force_me说明
写自己的数据true后台做操、打卡累计、实时缓冲区
读自己的数据true 或 false明确要读自己时用 true;由看板当前选择决定时用 false
读粉丝的数据false看板渲染粉丝图表
写粉丝的数据true(例外)不是制度性行为,未来若出现“UP 主修改粉丝打卡记录”这类功能,才需要特殊处理

 

1.4 force_me 的传播路径

force_me 不仅在 health_dat_filename 里使用,还会沿着调用链向上传递。设计原则是:

上层决定“这次操作是不是强制走自己”,把 force_me 原样传给下层,下层不再做二次判断。

典型调用链:

// 上层:后台做操,明确知道自己要写自己的数据
void do_workout_realtime() {
    save_workout_data(true /* force_me */);
}

// 中层:根据 force_me 选择目录,并传递给下一层
void save_workout_data(bool force_me) {
    // 可能做其他与目录无关的准备
    std::string path = health_dat_filename(force_me, t);
    // 可能继续传给其他函数
    write_health_file(force_me, path);
}

// 下层:最终拼接路径
std::string health_dat_filename(bool force_me, time_t t) const {
    const std::string& dir = force_me ? me_health_dir_ : curr_health_dir_;
    // ...
}

关键约束:

  1. force_me 必须原样传递,中途不得翻转、不得重新判断。
  2. 上层如果在某个分支里改用了 true,那么下游所有相关调用都要用 true,保持一致。
  3. 不允许出现“上层传 true,下层根据某些条件自行改成 false”的情况——这会破坏语义。

 

1.5 调用方必须注意的事项

1.5.1 明确自己在操作谁的数据

调用任何带 force_me 的函数前,先问自己:

“这次操作的数据来源,是必须是我自己的,还是由看板当前选择决定?”

  • 后台任务、实时写入、打卡累计更新 → 必须用 true
  • 看板渲染、图表展示、用户切换后刷新 → 用 false

 

1.5.2 线程约束

  • 所有对 healthxxx.dat 的读写都发生在主线程。
  • 单次调用内完成读或写,函数退出时操作已结束。
  • me_health_dir_ 是 const,后台任务用 force_me == true 访问它是安全的。
  • curr_health_dir_ 只在主线程修改,调用方需保证不跨线程修改它。

 

1.5.3 不要用 curr_health_dir_ 的当前值去推断 force_me

curr_health_dir_ 是一个运行时状态,它可能因为看板切换而随时改变。用它的当前值来决定 force_me 是危险的:

// 错误:用 curr_health_dir_ 推断 force_me
bool force_me = (curr_health_dir_ == me_health_dir_);
health_dat_filename(force_me, t);

这段代码看起来“聪明”,但它把“看板当前是不是在看自己”和“这次操作要不要强制走自己”混为一谈了。一旦看板切换,force_me 的结果就会变,后台任务的行为也会跟着变。

正确做法是:由调用者根据业务语义显式决定 force_me。

// 正确:后台任务明确知道自己要强制走自己
health_dat_filename(true, t);

// 正确:看板渲染明确知道自己走当前选中
health_dat_filename(false, t);

 

1.5.4 如果未来支持多账号

force_me 的语义保持不变——“绕过当前选择,强制使用自己的目录”。如果未来支持多账号,me_health_dir_ 可能不再是全局常量,而是按当前账号派生。但调用方不需要感知这个变化,只需继续按业务语义传 true/false。

 

1.6 例外情况:写粉丝数据

未来可能出现非制度性的写粉丝数据场景,例如:

  • UP 主手动修正某次粉丝做操的打卡记录;
  • UP 主给粉丝的某次做操补一条本地备注(不是评语,而是本地备注);
  • 平台层面的数据修复。

这些属于意外情况,不是常规流程。遇到这类需求时,不应该直接改 force_me 的默认值,而应该在调用处显式传 false 并加注释说明“这是一次例外操作”。

// 例外:UP 主手动修正粉丝 A 的某次打卡记录。
// 这不是制度性行为,仅在明确的修复场景下调用。
health_dat_filename(false /* force_me */, t);

注意: 这里的 false 意味着“走 curr_health_dir_”,因此调用前必须确保 curr_health_dir_ 确实指向目标粉丝的目录,且调用后不再被看板切换打断。

 

1.7 一句话总结

force_me == true 表示“强制使用自己的目录”,force_me == false 表示“使用看板当前选中的目录”。

原则上,写操作走 true(写自己的数据),读操作才可能需要 false(读当前选中的用户)。

UP 主不改粉丝的 .dat;评语是发给粉丝、由粉丝侧写入自己的文件。

未来若出现写粉丝数据的特殊需求,属于例外,需在调用处显式说明,不改变默认原则。

全部评论: 0 条

    写评论: