TL;DR 概览
ANORA的设计花费了比预期更多的时间,目前还未达到目标
本期目标
进展速记(Changelog)
新增:
变更:
修复:
主要进展内容
ANORA的设计难题
虽然四天前我说“毕竟这些东西当时我用了好几个月内的工作间隙。但我觉得有善用claude应该能及时完成”,但实际上比我想象得更难,仅仅是设计就几乎花掉了这一期所有时间
设计文档中的致命问题
我第一天写了ANORA的设计文档。我以为已经很全面了,毕竟我是基于之前已经实现的AAW的经验,覆盖了各种实体的定义、执行逻辑、功能细节、项目结构等等
但是当我用claude检查的时候,我发现了很多致命问题
问题1:推还是拉?
在ANORA的前身AAW里,逻辑图里的顺序是“拉”的,即执行器只记录节点间的依赖关系,当某个节点所依赖的节点都执行完成后,就把数据从其依赖的节点的出Port上拉到这个节点的入Port上供这个节点使用
ANORA为了给节点更多的自由度,使节点可以根据当前入Port输入状态决定自己是否可以执行(为了提供现代编程语言的可选参数特性),这就必须使用“推”的,即一个节点执行完成后会把它出Port上的数据推到下一个节点的入Port上,然后节点才能做出判断
但这产生了一个问题:ANORA需要和AAW一样支持Port的嵌套。AAW在拉的模式下可以先拉外层Port再拉里层Port来保证里层Port的数据优先级最高,以此支持入Port数据模板之类的写法。但ANORA的推的模式就无法支持这个特性了
问题2:要不要支持Port的嵌套?
遇到上述问题后,我首先想的就是“问题的前提条件是否是无法改变的”,也就是是否一定要支持Port的嵌套。直接用get/set节点行不行
思考后我觉得是必须要支持的。因为我设计时就想好了这个项目的演示用后端——API录制回放。是的,还是AAW。但不是因为有个垃圾底层系统需要我包装,而是因为这个任务同时涉及了强时序、复杂数据流、异步调用这三个节点编程系统能发挥最大作用但又最难同时满足的场景,它会成为ANORA的试金石
而为了适配复杂数据流,“从默认数据模板中改一个数据”的操作必须要足够简便,最多只能用独立的参数节点提供默认数据模板,不能通过额外的节点数据合并。毕竟合并数据如果不使用嵌套Port,就必须通过某种更程序员的方式指定属性路径了,这显然和ANORA的设计目的相悖。
所以问题1必须要解决。但在思考解决方案的时候,我遇到了另一个更严重的问题
问题3:同步还是异步?
在ANORA的前身AAW里,逻辑图的执行是全顺序的,即使在一个批次里,节点的执行也是顺序的。当时这个设计是因为我没有很多时间实现一个复杂高效的系统,所以当时我就想只要在性能可以接受的情况下debug尽可能方便就行。但是ANORA自然不能这样,所以在我最初的设计中执行是全异步的。这使效率最大化,而且不管是用在什么任务中都足以胜任。
只能说理想是美好的。在全异步的模式下,我想不到一个可以同时满足易用、直观、可维护的循环逻辑执行方案。我参考了UE、ComfyUI等流行的节点编程系统,但它们要么就是直接封装编程语言里的while为节点,要么就是根本不支持流程控制。
问题4:要不要null?
这反而是最小的问题了,这玩意又不是给经验丰富开发者用的,拒绝null那就得引入更多保护措施,反而导致其上手门槛更多。一番折腾后我才明白了这个道理,然后释然地添加了null
解决方案?
显然,问题2和问题4都解决了,关键是1和3。
最后,我想到了这么一个设计:让流程控制节点可以一次激活多次执行
而这个方案不仅解决了两个问题,还让ANORA彻底甩开了AAW到达了全新层面。
ANORA的思想
更复杂的节点
之前我一直把节点当成函数调用的抽象,这使我陷入了误区——调用函数的语句天生就没有分支!
虽然在AAW里通过“以数据为核心,节点只有其需求的数据都输入后才能运行,运行后可以选择性地在特定出边输出数据”的方式实现了比UE更易用分支写法,但是其循环逻辑仍然十分粗糙、难以编写——它产生了环
而有环的图天生就是不直观的
但当我不再把节点当成一个函数,而是以更自然的方式进行比喻时,一切迎刃而解了