VedAstro架构解析:深入理解核心公式 event = (data + logic) × time 完整指南
VedAstro架构解析:深入理解核心公式 event = (data + logic) × time 完整指南
VedAstro 是一个非营利开源吠陀占星(Vedic Astrology)项目,把上千条古老的占星规则变成了可计算的人生预测引擎。它的心脏只有一条公式:
event = (data + logic) × time
用大白话翻译:事件 = (数据 + 逻辑)× 时间。
- data(数据):写在 XML 里的事件规则——这是什么事件、吉凶如何、描述是什么
- logic(逻辑):C# 里的计算方法——这个事件在什么天文条件下发生
- time(时间):运行时输入的时间范围——把"规则"变成"某年某月某日真的会发生"
这篇文章不要求你会编程,带你 5 分钟看懂 VedAstro 是如何用一条公式生成人生预测图的。🔭
公式三要素拆解:data、logic 和时间各是什么?
data:写在 XML 里的"事件规则表"
所有事件的"档案"都存放在 EventDataList.xml 中,这个文件超过 8900 行,每条 <Event> 节点就是一条占星规则。比如"适合出行的月亮日"这条规则长这样:
<Event>
<Name>GoodLunarDayForTravel</Name>
<Nature>Good</Nature>
<Description>
For journeys/travel the best lunar days are the 2nd, 3rd, 5th...
</Description>
<Tag>Travel</Tag>
</Event>
注意这里只有"它是什么",没有"怎么算":名称、吉凶(Nature)、描述文字、分类标签(Tag)。数据与计算被刻意分开了,这是整个架构的关键。
logic:用"特性"一对一绑定的计算方法
那"怎么算"呢?答案在 Muhurtha.cs 这个近 1 万行的文件里,每个静态方法就是一个事件的判断逻辑。以"适合出行的月亮日"为例:
[EventCalculator(EventName.GoodLunarDayForTravel)]
public static CalculatorResult GoodLunarDayForTravel(Time time, Person person)
{
var lunarDayNumber = Calculate.LunarDay(time).GetLunarDayNumber();
var rightLunarDay = lunarDayNumber == 2 || lunarDayNumber == 3
|| lunarDayNumber == 5 || lunarDayNumber == 7
|| lunarDayNumber == 10 || lunarDayNumber == 11
|| lunarDayNumber == 13;
return new() { Occuring = rightLunarDay };
}
三个角色把数据和逻辑"焊"在一起:
- EventName 枚举(EventName.cs):每个事件名称在代码里都有对应的枚举值,相当于事件的"身份证号"
- EventCalculatorAttribute 特性(EventNameAttribute.cs):像标签一样贴在计算方法上,声明"我是为哪个事件服务的"
- EventCalculatorDelegate 委托(CalculatorDelegates.cs):统一签名
CalculatorResult (Time, Person),让上千个不同算法能被同一种方式调用
启动时,程序会读取 XML 里的每个事件名,用反射找到挂着同名特性的方法并绑定——数据找到了它的逻辑,这一步就完成了。
time:把"规则"变成"发生"的扳机
Time 结构体同时保存了标准时间和地理位置(占星计算必须基于出生地的本地平太阳时)。当你在页面上选择"从 1982 年到 2015 年"并点击 Calculate 时,GetTimeListFromRange 会把这个范围按设定精度切成一片一片的时间片——每一片都是逻辑要检查一次的时刻。
这就是公式里为什么是 × time 而不是 + time:同一套(数据 + 逻辑)要在每一个时间片上重复判断,同一个规则在不同时刻得出不同结果,时间的"乘法"把静态规则放大成贯穿一生的事件序列。
公式如何运转:两步生成一个 Event
整个流程在 EventManager.cs 中编排,官方架构图在 README.md:
第 1 步:data + logic 绑定
XML 数据经 EventData.FromXml 解析后,生成 EventData 结构——它同时持有"事件档案"(名称、吉凶、描述、标签)和"计算方法"(EventCalculator 委托)。此时还没有做任何占星计算,只是完成了链接。
第 2 步:× time 扫描
GetEventsInTimePeriod 拿到时间片列表后,用并行线程对每个时间片调用 eventData.EventCalculator(time, person):
- 若返回"正在发生" → 记下一个 EventSlice(临时事件切片)
- 连续为真的时间片被合并成一个完整区间
最后 EventSlicesToEvents 把每个"持续区间"封装成最终的 Event 对象——带开始时间、结束时间、吉凶、描述。这就是你在界面上看到的一条条事件。
💡 性能小彩蛋:并行扫描让一次全人生计算从 143 秒缩短到 40 秒(见 EventManager.cs 注释)。
全局视角:公式在 VedAstro 四大组件中的位置
这条公式运行在 CORE LIBRARY(即 Library/ 目录)中,向外辐射出三种形态:
| 形态 | 目录 | 说明 |
|---|---|---|
| Web 网站 | Website/ | Blazor 前端,普通用户直接使用 |
| REST API | API/ | 200+ 端点,供开发者集成 |
| .NET 库 | Library/ | 可直接引用进你自己的项目 |
为什么计算放在服务器而不是浏览器?因为一次完整的人生事件计算约需 1GB 内存、30% CPU(README.md 设计笔记),手机浏览器扛不住,所以 API 路线成为定案。
公式的产物:一张人生预测图
下面这张图就是 event = (data + logic) × time 跑完全程的输出——Steve Jobs 1982 至 2015 年的人生事件图:
横轴是时间(time 维度的展开),每一根绿/红竖条都是一个由公式生成的 Event:绿色为吉事、红色为凶事,标注了"离开苹果""回归苹果""癌症手术"等真实人生节点。你可以看到成千上万个事件如何从一条公式里"长"出来。📊
快速定位:公式每个部分的源码在哪?
想动手验证?按下表直接找到对应文件:
| 公式部分 | 文件路径 |
|---|---|
| 公式原文与流程图 | README.md |
| data:事件规则 XML | Library/XMLData/EventDataList.xml |
| 事件名枚举(ID 卡) | Library/Data/Enum/EventName.cs |
| logic:计算方法集合 | Library/Logic/Calculate/Muhurtha.cs |
| 绑定特性 | Library/Data/EventNameAttribute.cs |
| 统一委托签名 | Library/Data/Delegate/CalculatorDelegates.cs |
| data+logic 绑定 | Library/Data/EventData.cs |
| × time 扫描编排 | Library/Logic/EventManager.cs |
| 时间片切分 | Library/Data/Time.cs |
| 最终事件对象 | Library/Data/Event.cs |
想新增一条预测? 按官方 Code Edit Guide 三步走:写一个计算方法 → 在 EventName 枚举加名字 → 在 XML 里补上规则数据,公式就会自动把它纳入计算。
一句话总结
data + logic 回答了"世界上有哪些可能发生的事",× time 回答了"它在你的哪一年真正发生"。
VedAstro 的架构智慧不在于算法有多神秘,而在于把占星知识拆成了"数据、逻辑、时间"三个正交的维度——任何一类规则都可以独立增改。这正是它能容纳数千种占星计算、并持续进化十年的原因。🌟



