(一)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閱讀 230,578評論 6 544
  • 序言:濱河連續發生了三起死亡事件,死亡現場離奇詭異,居然都是意外死亡,警方通過查閱死者的電腦和手機,發現死者居然都...
    沈念sama閱讀 99,701評論 3 429
  • 文/潘曉璐 我一進店門,熙熙樓的掌柜王于貴愁眉苦臉地迎上來,“玉大人,你說我怎么就攤上這事。” “怎么了?”我有些...
    開封第一講書人閱讀 178,691評論 0 383
  • 文/不壞的土叔 我叫張陵,是天一觀的道長。 經常有香客問我,道長,這世上最難降的妖魔是什么? 我笑而不...
    開封第一講書人閱讀 63,974評論 1 318
  • 正文 為了忘掉前任,我火速辦了婚禮,結果婚禮上,老公的妹妹穿的比我還像新娘。我一直安慰自己,他們只是感情好,可當我...
    茶點故事閱讀 72,694評論 6 413
  • 文/花漫 我一把揭開白布。 她就那樣靜靜地躺著,像睡著了一般。 火紅的嫁衣襯著肌膚如雪。 梳的紋絲不亂的頭發上,一...
    開封第一講書人閱讀 56,026評論 1 329
  • 那天,我揣著相機與錄音,去河邊找鬼。 笑死,一個胖子當著我的面吹牛,可吹牛的內容都是我干的。 我是一名探鬼主播,決...
    沈念sama閱讀 44,015評論 3 450
  • 文/蒼蘭香墨 我猛地睜開眼,長吁一口氣:“原來是場噩夢啊……” “哼!你這毒婦竟也來了?” 一聲冷哼從身側響起,我...
    開封第一講書人閱讀 43,193評論 0 290
  • 序言:老撾萬榮一對情侶失蹤,失蹤者是張志新(化名)和其女友劉穎,沒想到半個月后,有當地人在樹林里發現了一具尸體,經...
    沈念sama閱讀 49,719評論 1 336
  • 正文 獨居荒郊野嶺守林人離奇死亡,尸身上長有42處帶血的膿包…… 初始之章·張勛 以下內容為張勛視角 年9月15日...
    茶點故事閱讀 41,442評論 3 360
  • 正文 我和宋清朗相戀三年,在試婚紗的時候發現自己被綠了。 大學時的朋友給我發了我未婚夫和他白月光在一起吃飯的照片。...
    茶點故事閱讀 43,668評論 1 374
  • 序言:一個原本活蹦亂跳的男人離奇死亡,死狀恐怖,靈堂內的尸體忽然破棺而出,到底是詐尸還是另有隱情,我是刑警寧澤,帶...
    沈念sama閱讀 39,151評論 5 365
  • 正文 年R本政府宣布,位于F島的核電站,受9級特大地震影響,放射性物質發生泄漏。R本人自食惡果不足惜,卻給世界環境...
    茶點故事閱讀 44,846評論 3 351
  • 文/蒙蒙 一、第九天 我趴在偏房一處隱蔽的房頂上張望。 院中可真熱鬧,春花似錦、人聲如沸。這莊子的主人今日做“春日...
    開封第一講書人閱讀 35,255評論 0 28
  • 文/蒼蘭香墨 我抬頭看了看天上的太陽。三九已至,卻和暖如春,著一層夾襖步出監牢的瞬間,已是汗流浹背。 一陣腳步聲響...
    開封第一講書人閱讀 36,592評論 1 295
  • 我被黑心中介騙來泰國打工, 沒想到剛下飛機就差點兒被人妖公主榨干…… 1. 我叫王不留,地道東北人。 一個月前我還...
    沈念sama閱讀 52,394評論 3 400
  • 正文 我出身青樓,卻偏偏與公主長得像,于是被迫代替她去往敵國和親。 傳聞我的和親對象是個殘疾皇子,可洞房花燭夜當晚...
    茶點故事閱讀 48,635評論 2 380

推薦閱讀更多精彩內容