(一)AnimatedDrawings分析,最强阅读源码方法分享

A Method for Animating Children's Drawings of the Human Figure(to appear in Transactions on Graphics and to be presented at SIGGRAPH 2023)

code is dumb, codemap is clever

前言

阅读源代码是软件工程师进阶的必备技能,但是现目前通过IDE阅读源代码的方式却令人痛苦不已。单行的代码大家都认识,初级的语法大多都不构成真正的障碍,然而阅读一个成熟或者开源项目,却往往令人倍感头疼。

复杂的嵌套关系,冗余的依赖结构,往往使只希望研究项目核心机制的我们,迷失在代码的汪洋之中。

 codemap是一款支持自动跳转,通过连线、高亮、标注等方式辅助阅读源代码的工具,通过 codemap,阅读源代码不再困难。

AnimatedDrawings 是facebook实验室发表的论文《A Method for Animating Children's Drawings of the Human Figure》的附属代码仓库,如果通过IDE来分析源代码,会让人倍感痛苦,但通过 codemap你会发现阅读源代码原来so easy!

一、安装

官网的readme文档中有详实的安装教程,按照指引能在本地快速搭建起项目的运行环境。但是,在2023.9.1,笔者在mac m2环境按照安装教程执行

pip install -e .

命令时,会报如下错误

Collecting PyYAML==6.0 (from animated-drawings==0.0.0)

  Using cached https://pypi.tuna.tsinghua.edu.cn/packages/36/2b/61d51a2c4f25ef062ae3f74576b01638bebad5e045f747ff12643df63844/PyYAML-6.0.tar.gz (124 kB)

  Installing build dependencies ... done

  Getting requirements to build wheel ... error

  error: subprocess-exited-with-error

  × Getting requirements to build wheel did not run successfully.

  │ exit code: 1

  ╰─> [48 lines of output]

      running egg_info

      writing lib/PyYAML.egg-info/PKG-INFO

      writing dependency_links to lib/PyYAML.egg-info/dependency_links.txt

      writing top-level names to lib/PyYAML.egg-info/top_level.txt

      Traceback (most recent call last):

        File "/Users/guopengshan/anaconda3/envs/animated_drawiings/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 353, in <module>

          main()

        File "/Users/guopengshan/anaconda3/envs/animated_drawiings/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 335, in main

          json_out['return_val'] = hook(**hook_input['kwargs'])

        File "/Users/guopengshan/anaconda3/envs/animated_drawiings/lib/python3.8/site-packages/pip/_vendor/pyproject_hooks/_in_process/_in_process.py", line 118, in get_requires_for_build_wheel

          return hook(config_settings)

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/build_meta.py", line 355, in get_requires_for_build_wheel

          return self._get_build_requires(config_settings, requirements=['wheel'])

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/build_meta.py", line 325, in _get_build_requires

          self.run_setup()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/build_meta.py", line 341, in run_setup

          exec(code, locals())

        File "<string>", line 288, in <module>

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/__init__.py", line 107, in setup

          return distutils.core.setup(**attrs)

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/_distutils/core.py", line 185, in setup

          return run_commands(dist)

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/_distutils/core.py", line 201, in run_commands

          dist.run_commands()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/_distutils/dist.py", line 969, in run_commands

          self.run_command(cmd)

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/dist.py", line 1233, in run_command

          super().run_command(command)

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/_distutils/dist.py", line 988, in run_command

          cmd_obj.run()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/command/egg_info.py", line 319, in run

          self.find_sources()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/command/egg_info.py", line 327, in find_sources

          mm.run()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/command/egg_info.py", line 549, in run

          self.add_defaults()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/command/egg_info.py", line 587, in add_defaults

          sdist.add_defaults(self)

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/command/sdist.py", line 113, in add_defaults

          super().add_defaults()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/_distutils/command/sdist.py", line 251, in add_defaults

          self._add_defaults_ext()

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/_distutils/command/sdist.py", line 336, in _add_defaults_ext

          self.filelist.extend(build_ext.get_source_files())

        File "<string>", line 204, in get_source_files

        File "/private/var/folders/q3/_l6msmp54b35hjdmmkq4x_jr0000gn/T/pip-build-env-p82sooh8/overlay/lib/python3.8/site-packages/setuptools/_distutils/cmd.py", line 107, in __getattr__

          raise AttributeError(attr)

      AttributeError: cython_sources

      [end of output]

  note: This error originates from a subprocess, and is likely not a problem with pip.

error: subprocess-exited-with-error

× Getting requirements to build wheel did not run successfully.

│ exit code: 1

╰─> See above for output.

通过debug,发现是PyYAML依赖版本异常导致,只需要将PyYAML升级为6.0.1即能恢复正常,如下图修改setup.py文件即可

二、IDE 实战分析

安装正常后,按照文档说明,在本地运行

from animated_drawings import render

render.start('./examples/config/mvc/interactive_window_example.yaml')

便能启动基础范例。

通常,我们会通过入口文件,在IDE中逐个文件或函数查看代码的具体实现机制。

比如 render.start('./examples/config/mvc/interactive_window_example.yaml') 这段代码,首先调用了配置文件interactive_window_example.yaml,然后通过 render 的 start 方法启动。

接着,我们会发现 interactive_window_example.yaml 又调用了 char_cfg.yaml、dab.yaml、fair1_ppf.yaml 文件,但各个文件的具体功能对于初学者的我们却无从得知。render.start 方法首先初始化了 cfg 实例,然后通过 cfg 中的具体配置,分别创建了 view、scene、controller 等实例,最后通过 controller.run() 启动。

在 controller.run 中首先执行了 self._prep_for_run_loop(),然后通过检测是否 self._is_run_over() 执行死循环,包括 self._start_run_loop_iteration()、self._update()、self._render()、self._tick()、self._handle_user_input()、self._finish_run_loop_iteration() 等更新算法,最后执行 self._cleanup_after_run_loop()。

而针对更具体的 self._prep_for_run_loop()、self._prep_for_run_loop() 等更新算法的实现机制,我们发现他们在 Controller 类中都是通过抽象方法 @abstractmethod 定义的,也就是说对于具体的执行流程,要查看具体执行的函数需要看在函数的运行流程中,具体是哪个 Controller 的子类被实例化了,那个子类实现的 self._prep_for_run_loop()、self._prep_for_run_loop() 等方法才是真实被执行的代码,因此,我们需要跳转到  render.py 文件中,查看 controller 的实例过程。

通过代码,我们发现controller的具体实现类是由 cfg.mode 决定,即当 config 中 mode 为 video_render 时,controller 为 VideoRenderController 的实例,当 mode 为 interactive 时,controller 为 InteractiveController 的实例。也就是说,具体执行的 self._prep_for_run_loop()、self._prep_for_run_loop() 等更新算法由 config 中的 mode 决定,当 mode 为 video_render 时,程序流中执行的就是 VideoRenderController 中的 self._prep_for_run_loop()、self._prep_for_run_loop(),否则就是 InteractiveController 中的 self._prep_for_run_loop()、self._prep_for_run_loop()。而具体的 mode 值,在 interactive_window_example.yaml 中,interactive_window_example.yaml 又依赖 char_cfg.yaml、dab.yaml、fair1_ppf.yaml,我们需要不停地在不同的文件夹中打开不同的文件,不停地查找、切换,才能验证这个最简单的范例究竟是怎么执行的。

为了这么简单的需求,我们不停地打开文件,不停地切换,对于初学者,大脑早就一团乱麻,不必要的思维负担已经消耗了绝大多数的脑力,而对于更重要的具体更新机制,却还一筹莫展。

code is dumb,这就是 codemap需要解决的问题!

三、 codemap 实战分析

 codemap摒弃了tab页的打开方式,通过平铺布局,以及连线、高亮、标注等一系列手段,辅助用户阅读源代码,告别了为了研究代码的执行机制,而不停切换文件,不断展开、折叠源代码的历史,为用户提供了一种可以清晰展示代码逻辑结构、添加高亮备注的方式。

 codemap目前已经支持js、ts、c、c++、java、golang、python等多种主流编程语言,未来还将支持更多。

单行的代码大家都认识,但是复杂的项目为什么就看不懂了呢?其中的关键在于阅读源代码的过程中,我们的大脑做了太多的无用功,在现有的ide中需要记忆大量无关紧要的中间函数、路径,甚至所在代码的行数,而通过codemap,我们能很好地解决这个问题。

1、回到上述问题,cfg.mode的具体取值影响了程序流,但在用户显式加载的配置文件interactive_window_example.yaml等中并没有mode配置项,着眼于程序示例1(蓝色标记),发现Config类会加载默认配置 mvc_base_cfg.yaml,在该文件中有配置项MODE: 'interactive',因此可以证实controller是InteractiveController的实例。

2、在程序示例2(蓝色标记)处,展示了controller.run()的执行步骤,但是具体的执行函数在Controller父类中都是抽象方法,具体的实现在子类InteractiveController中。

3、如图示例3(蓝色标记),InteractiveController会依次执行:

_prep_for_run_loop:更新self.prev_time为当前时间

_is_run_over:通过glfw.window_should_close(self.view.win)来判断是否需要执行以下循环

_start_run_loop_iteration:调用self.view.clear_window(),实际是通过OpenGL清理窗口

_update:通过平移、旋转、缩放矩阵相乘,求最新的矩阵

_render:首先通过view._update_shaders_view_transform()等更新camera信息,然后调用scene.draw()渲染

_tick:更新时间

_handle_user_input:处理用户输入

_finish_run_loop_iteration:交换缓存

_cleanup_after_run_loop:执行清理程序

四、code is dumb,  codemap is clever

通过 codemap  https://codemap.info 进行分析,我们已经大致地了解了AnimatedDrawings的启动流程,而且期间不会再因为不停地打开、切换文件,不停折叠代码、不停跳转而烦恼了,这次进行了分析,下次还能在今天的基础上继续研究。

通常,对于越复杂的项目, codemap的效果越好,想象一下,你要打开几十个文件,每个文件里面都是你不熟悉的类和函数,对于他们之间的调用、依赖关系,如果纯用脑力,我相信你会爆炸的。

©著作权归作者所有,转载或内容合作请联系作者
  • 序言:七十年代末,一起剥皮案震惊了整个滨河市,随后出现的几起案子,更是在滨河造成了极大的恐慌,老刑警刘岩,带你破解...
    沈念sama阅读 216,402评论 6 499
  • 序言:滨河连续发生了三起死亡事件,死亡现场离奇诡异,居然都是意外死亡,警方通过查阅死者的电脑和手机,发现死者居然都...
    沈念sama阅读 92,377评论 3 392
  • 文/潘晓璐 我一进店门,熙熙楼的掌柜王于贵愁眉苦脸地迎上来,“玉大人,你说我怎么就摊上这事。” “怎么了?”我有些...
    开封第一讲书人阅读 162,483评论 0 353
  • 文/不坏的土叔 我叫张陵,是天一观的道长。 经常有香客问我,道长,这世上最难降的妖魔是什么? 我笑而不...
    开封第一讲书人阅读 58,165评论 1 292
  • 正文 为了忘掉前任,我火速办了婚礼,结果婚礼上,老公的妹妹穿的比我还像新娘。我一直安慰自己,他们只是感情好,可当我...
    茶点故事阅读 67,176评论 6 388
  • 文/花漫 我一把揭开白布。 她就那样静静地躺着,像睡着了一般。 火红的嫁衣衬着肌肤如雪。 梳的纹丝不乱的头发上,一...
    开封第一讲书人阅读 51,146评论 1 297
  • 那天,我揣着相机与录音,去河边找鬼。 笑死,一个胖子当着我的面吹牛,可吹牛的内容都是我干的。 我是一名探鬼主播,决...
    沈念sama阅读 40,032评论 3 417
  • 文/苍兰香墨 我猛地睁开眼,长吁一口气:“原来是场噩梦啊……” “哼!你这毒妇竟也来了?” 一声冷哼从身侧响起,我...
    开封第一讲书人阅读 38,896评论 0 274
  • 序言:老挝万荣一对情侣失踪,失踪者是张志新(化名)和其女友刘颖,没想到半个月后,有当地人在树林里发现了一具尸体,经...
    沈念sama阅读 45,311评论 1 310
  • 正文 独居荒郊野岭守林人离奇死亡,尸身上长有42处带血的脓包…… 初始之章·张勋 以下内容为张勋视角 年9月15日...
    茶点故事阅读 37,536评论 2 332
  • 正文 我和宋清朗相恋三年,在试婚纱的时候发现自己被绿了。 大学时的朋友给我发了我未婚夫和他白月光在一起吃饭的照片。...
    茶点故事阅读 39,696评论 1 348
  • 序言:一个原本活蹦乱跳的男人离奇死亡,死状恐怖,灵堂内的尸体忽然破棺而出,到底是诈尸还是另有隐情,我是刑警宁泽,带...
    沈念sama阅读 35,413评论 5 343
  • 正文 年R本政府宣布,位于F岛的核电站,受9级特大地震影响,放射性物质发生泄漏。R本人自食恶果不足惜,却给世界环境...
    茶点故事阅读 41,008评论 3 325
  • 文/蒙蒙 一、第九天 我趴在偏房一处隐蔽的房顶上张望。 院中可真热闹,春花似锦、人声如沸。这庄子的主人今日做“春日...
    开封第一讲书人阅读 31,659评论 0 22
  • 文/苍兰香墨 我抬头看了看天上的太阳。三九已至,却和暖如春,着一层夹袄步出监牢的瞬间,已是汗流浃背。 一阵脚步声响...
    开封第一讲书人阅读 32,815评论 1 269
  • 我被黑心中介骗来泰国打工, 没想到刚下飞机就差点儿被人妖公主榨干…… 1. 我叫王不留,地道东北人。 一个月前我还...
    沈念sama阅读 47,698评论 2 368
  • 正文 我出身青楼,却偏偏与公主长得像,于是被迫代替她去往敌国和亲。 传闻我的和亲对象是个残疾皇子,可洞房花烛夜当晚...
    茶点故事阅读 44,592评论 2 353

推荐阅读更多精彩内容