倉庫:https://gitee.com/mrxiao_com/2d_game_5
回顧之前的內容,并遇到了一次一階異常(First-Chance Exception)。
歡迎來到新一期的開發過程,我們目前正在編寫調試接口代碼。
當前,我們已經在布局系統上進行了一些工作,今天計劃進一步優化它。此外,我們還希望研究如何與性能分析(profiling)代碼的接口進行交互。目前,我們擁有大量的分析數據,但由于缺乏合適的界面,難以快速瀏覽和提取有用的信息。因此,今天的目標是開始構建一個交互式的調試界面,使我們可以更高效地瀏覽和利用這些數據。
當前的調試系統已經具備了大部分核心功能,但仍然需要進一步優化,以確保其能夠滿足我們的需求。因此,我們會以分析數據的可視化作為切入點,推動整個調試界面的改進。
當前進度
-
調試環境設置
- 打開命令行,查看當前目錄,并啟動調試工具。
- 編譯并運行代碼,確保環境正常。
-
回顧上次的進展
- 之前的改進主要是讓交互系統更加統一,使不同的界面元素可以共享相同的交互邏輯。
- 另外,布局系統也得到了增強,現在能夠自動排列界面元素,使界面組織更加有序。
-
接下來的目標
-
優化性能分析數據的可視化展示
- 目前,性能分析數據僅以基本的文本或數值方式呈現,難以從中快速獲取有效信息。
- 目標是開發一個更友好的界面,使數據的展示更具可讀性,并允許快速導航。
-
探索更高級的界面交互方式
- 需要思考如何讓用戶能夠更靈活地操作調試界面,例如添加篩選、排序、層級展開等功能。
- 目前尚未完全確定最佳方案,需要進行一定的實驗和探索。
-
總的來說,今天的主要任務是繼續優化調試界面,并開始為性能分析數據提供更友好的展示方式。
調查這個異常的原因。
在當前的調試過程中,我們遇到了first-chance exception(第一次改變的異常),這表明某個記錄無效。經過分析,發現該異常與性能分析(profiling)代碼有關。
目前,我們的調試模式并未提供足夠的保護,因為字符串數據沒有被安全存儲,而是作為臨時數據處理。這意味著當動態鏈接庫(DLL)重新加載時,可能會發生調試記錄被移除的情況,導致異常。
分析問題發生的可能性
-
數據更新時機存在潛在問題
- 調試記錄的更新時間在
DebugFrameEnd
之后,但渲染發生在GameUpdateAndRender
里。 - 如果在
DebugFrameEnd
和GameUpdateAndRender
之間執行了DLL重載,那么某些記錄可能會被移除,導致異常。
- 調試記錄的更新時間在
-
代碼結構導致潛在的不一致
- 當前的調試框架分散在多個地方:
DebugFrameEnd
負責調試數據的收集和整理。GameUpdateAndRender
負責渲染調試信息。
- 這種分離可能導致數據同步問題,從而引發崩潰或錯誤。
- 當前的調試框架分散在多個地方:
改進方案
考慮到當前調試代碼的組織方式,我們提出了一種優化方案:
- 將調試渲染(debug overlay)與
DebugFrameEnd
進行整合,而不是在GameUpdateAndRender
里執行。 - 這樣可以保證渲染時數據是最新的,同時避免因DLL重載導致的數據丟失問題。
- 另外,由于我們的幀處理邏輯與顯示完全解耦,因此可以更自由地調整執行順序,而不影響整體運行。
下一步計劃
- 調整代碼邏輯,將調試渲染邏輯移至
DebugFrameEnd
。 - 優化代碼結構,減少調試代碼的分散程度,使其更加清晰易維護。
- 觀察改動后的效果,驗證是否有效減少異常發生的可能性,并確保調試系統仍然穩定運行。
最終目標是簡化代碼結構,同時提升調試系統的可靠性,避免因數據更新不一致導致的問題。這一改動不僅讓代碼更加整潔,還能提升調試體驗,使調試信息更加直觀和實時。
在 win32_game.cpp
文件中,將 DebugCollation
移到 FramerateWait
之前,并將 NewInput
和 &Buffer
傳遞給 DEBUGFrameEnd
。
我們計劃對調試系統的代碼結構進行調整,以優化其組織方式并減少對游戲主邏輯的依賴。具體來說,我們希望將調試代碼的位置向前移動,使其在幀等待(frame wait)之前執行,從而完全獨立于游戲邏輯。
調整方案
-
將調試代碼從
GameUpdateAndRender
中抽離- 目前,
GameUpdateAndRender
負責更新游戲狀態并進行渲染,同時還調用了一些調試相關的代碼。 - 我們不希望調試系統與游戲邏輯相互耦合,因此計劃將其完全獨立。
- 目前,
-
在
DebugFrameEnd
之前執行調試代碼- 在幀等待(frame wait)之前,我們已經有
DebugCollation
,負責整理調試信息。 - 現在,我們計劃將
DebugFrameEnd
直接放在這里,并讓它接收GameUpdateAndRender
需要的繪制緩沖區(buffer)。 - 這樣,調試系統將獨立處理游戲數據,而不會干擾游戲的更新和渲染邏輯。
- 在幀等待(frame wait)之前,我們已經有
-
清理
GameUpdateAndRender
內部的調試調用- 之前,游戲邏輯內部直接調用了一些調試函數,比如
DebugFrameEnd
。 - 但這樣做會導致游戲邏輯與調試系統耦合,使代碼變得不清晰、不易維護。
- 通過調整結構,我們讓游戲邏輯僅負責寫入緩沖區,調試系統再從緩沖區中讀取數據并執行調試任務。
- 之前,游戲邏輯內部直接調用了一些調試函數,比如
調整后的優勢
- 調試系統完全獨立,不再直接調用游戲邏輯,游戲邏輯也不依賴調試代碼。
- 代碼更加整潔,減少
GameUpdateAndRender
內部的調試調用,使主游戲邏輯更專注于游戲更新和渲染。 - 更好的擴展性,未來如果需要調整調試系統,不需要修改游戲主邏輯,降低了維護成本。
下一步計劃
- 驗證修改后調試系統是否正常運行,確保
DebugFrameEnd
仍能正確處理數據。 - 檢查是否有其他依賴于
GameUpdateAndRender
的調試代碼,并進行必要的調整,以確保所有調試功能都在新架構下正常工作。 - 觀察運行效果,確認調試信息的顯示仍然準確,并進行必要的優化。
此次改動的核心目標是提高代碼的模塊化和可維護性,同時確保調試系統與游戲邏輯解耦,使整體架構更加合理。
在 game.cpp
中移除 DEBUGStart
和 DEBUGEnd
。
我們目前的調整遇到了一個問題,不過我們稍后會處理它。現在,我們先進行一些清理工作,以簡化調試代碼結構。
當前調整
-
去除
DebugStart
和DebugEnd
- 這些函數在當前方案中暫時不再需要,因此我們先將它們移除,以減少不必要的代碼。
- 之后,我們會重新評估它們的作用,看看是否需要以新的方式引入它們。
-
保留
DebugCollation
和DebugFrameEnd
作為主要調試流程DebugCollation
仍然用于整理調試信息。DebugFrameEnd
現在獨立運行,不再嵌套在GameUpdateAndRender
內部,而是在幀等待(frame wait)之前執行。
下一步計劃
- 解決當前遇到的問題,確保新的架構能夠正常運作。
- 觀察代碼運行情況,確認
DebugFrameEnd
仍然可以正確處理調試數據,并且調試信息能夠正確顯示。 - 進一步優化調試系統,看看是否需要新的方法來管理
DebugStart
和DebugEnd
,或者以更合理的方式替代它們。
我們的目標是讓調試代碼更加清晰、獨立,從而提高代碼的可維護性和可擴展性。
在 game_debug.cpp
中,在 DEBUGGameFrameEnd
內部調用 DEBUGStart
和 DEBUGEnd
。
我們目前正在對 game_debug
進行調整,使其代碼結構更加合理,減少不必要的調用和依賴,并將 DebugStart
和 DebugEnd
整合到 DebugFrameEnd
中,以確保調試流程更加緊湊和清晰。
當前調整
-
移動
DebugStart
和DebugEnd
到DebugFrameEnd
內部- 之前
DebugStart
和DebugEnd
是獨立調用的,我們現在將它們直接集成到DebugFrameEnd
之中,使其成為調試系統的一部分,而不是外部調用。 - 這樣可以減少額外的函數調用,使代碼更加緊湊。
- 之前
-
修改
DebugFrameEnd
以接收更多參數- 現在
DebugFrameEnd
需要接受DebugState
作為參數,這樣可以直接在內部訪問調試狀態,而不需要全局訪問。 - 另外,我們還需要確保
DebugFrameEnd
具備繪制所需的DrawBuffer
和NewInput
,以便完成最終的調試渲染。
- 現在
-
調整
DebugFrameEnd
調用順序- 目前
DebugFrameEnd
直接在主循環內調用,之前的DebugCollation
可能需要提前執行,以確保數據整理過程在渲染之前完成。 - 這樣可以避免在渲染階段處理未整理的數據,提高調試信息的完整性。
- 目前
-
優化
game_debug.h
- 移除不必要的導出函數,如
RefreshCollation
,因為它并沒有被其他模塊調用。 - 清理
game_debug.h
頭文件,使其更加簡潔,確保只有必要的內容被暴露給其他模塊。
- 移除不必要的導出函數,如
下一步計劃
- 測試新的
DebugFrameEnd
結構,確保它能夠正確處理DebugState
并渲染調試信息。 - 優化
DebugCollation
的調用位置,確保數據整理發生在正確的時間點。 - 進一步簡化
game_debug.h
,減少不必要的外部接口,使game_debug.cpp
內部邏輯更加自洽。
我們的目標是讓調試代碼更加模塊化、清晰,并減少不必要的耦合,這樣可以提高可維護性,并避免潛在的錯誤。
在 game.cpp
中引入 DEBUGGetGameAssets
,讓調試系統能夠訪問 TranState->Assets
。
在當前的開發過程中,接下來的任務是使調試系統能夠直接訪問游戲中的資產。為了實現這一目標,我們打算讓調試系統可以直接獲取游戲的資產,而不是讓游戲主動調用調試系統。這樣做的好處是,調試系統會獨立于游戲,只有在調試系統啟用時,它才會與游戲進行交互;如果調試系統被編譯移除,游戲將完全不受影響,這使得調試和游戲代碼的分離更加清晰,并且有助于在最終發布時去除不需要的調試代碼。
具體步驟:
-
為調試系統提供獲取游戲資產的接口:
- 通過在游戲代碼中提供一個簡單的
get_game_assets
函數,調試系統可以直接訪問游戲的資源。這一函數會接收游戲的內存結構作為參數,然后直接從游戲內存中提取相關的資產數據。
- 通過在游戲代碼中提供一個簡單的
-
簡化調試系統與游戲的關系:
- 這種方式的關鍵是讓調試系統能夠主動從游戲中獲取數據,而不是游戲向調試系統發起調用。這使得調試系統與游戲代碼之間的耦合度降低,游戲本身也不需要了解調試系統的細節。
-
代碼實現:
- 在游戲代碼中,增加一個簡單的獲取游戲資產的接口。比如一個
get_game_assets
函數,它從游戲內存中讀取并返回游戲的資產數據。只要資產被正確初始化,調試系統就能通過這個接口訪問到它們。
- 在游戲代碼中,增加一個簡單的獲取游戲資產的接口。比如一個
-
簡化并提高代碼可維護性:
- 通過這種方式,調試系統與游戲邏輯之間的界限變得更加明確,調試系統的開關也變得更加簡單。如果需要在發布版本中移除調試功能,完全不需要修改游戲的邏輯,只需從編譯中排除調試代碼即可。
總結:
這種做法的最大優勢在于,它使得調試系統不再依賴于游戲主動調用它,而是通過游戲提供的接口來主動訪問必要的數據。這樣不僅清晰地分離了調試系統與游戲邏輯,而且還為以后的調試代碼移除提供了更好的支持。
在 game_platform.h
中,使 DEBUG_GAME_FRAME_END
的調用結構與 GAME_UPDATE_AND_RENDER
相同。
在當前的工作中,主要目標是確保調試系統可以順利獲取游戲中的資產,并且確保輸入和繪制緩沖區的數據能順利傳遞。為了實現這一目標,關鍵是要確保在游戲更新和渲染過程中,所有相關的數據(例如游戲內存、屏幕寬高、繪制緩沖區和輸入信息)都能正確地傳遞給調試系統。
主要步驟:
-
確保游戲更新與渲染的結構一致:
- 需要調整游戲的更新和渲染流程,確保調試系統可以像渲染一樣,正確獲取游戲內存和所有必要的數據(如輸入和繪制緩沖區)。這意味著在更新和渲染的過程中,所有這些數據都需要通過相同的調用結構傳遞。
-
調整調用結構:
- 調整代碼結構,確保在進行游戲更新和渲染時,輸入和繪制緩沖區能夠被正確傳遞給調試系統。這樣就能確保調試系統可以獨立地訪問這些數據,而不需要過多的耦合。
-
考慮代碼命名:
- 為了更清晰地表達這層關系,可以將調試系統的相關代碼命名為
debug_update_and_render
或類似的名稱,以便更好地體現出它與正常渲染流程的一致性。
- 為了更清晰地表達這層關系,可以將調試系統的相關代碼命名為
-
簡化數據傳遞:
- 通過確保傳遞游戲內存、寬高、輸入和繪制緩沖區這些關鍵信息到調試系統,調試系統就能有效地執行所需的操作,而不需要過多依賴于其他復雜的邏輯或流程。
總結:
核心思路是通過確保調試系統與游戲的更新和渲染過程有一致的調用結構,使得調試系統能夠獨立地接收到所需的所有數據,簡化調試代碼與游戲邏輯之間的耦合。這樣做不僅能夠提高代碼的清晰度,還能方便未來的擴展和調試功能的移除。
在 game_debug.cpp
中為調試系統創建 DrawBuffer
。
在當前的工作中,主要目的是確保在調試過程中,能夠正確地使用緩沖區來繪制數據,并且確保調試系統和游戲渲染系統能夠有效地協同工作。具體步驟如下:
主要步驟:
-
創建全屏緩沖區:
- 需要在游戲的渲染系統中,創建一個全屏的緩沖區來進行繪制操作。通過使用現有的緩沖區(如離屏緩沖區),可以獲得寬度和高度等信息,這樣就可以為調試系統創建一個相應的緩沖區。
-
設置緩沖區參數:
- 在渲染之前,需要確保將寬度、高度和內存地址等參數正確設置到緩沖區中。這樣,調試系統可以正確地將數據繪制到緩沖區,并在屏幕上顯示出來。
-
簡化調試功能:
- 將調試系統的繪制過程分離出來,確保它獨立于游戲的其他渲染邏輯。這使得調試系統更加簡潔,并且能在游戲中進行高效的調試操作,而不需要依賴于復雜的游戲邏輯。
-
調整函數和參數:
- 確保函數調用正確,特別是確保調試結束的函數
debug_end
接受正確的參數。通過適當調整函數接口,使得代碼更加清晰和易于維護。
- 確保函數調用正確,特別是確保調試結束的函數
-
確保緩沖區的可訪問性:
- 需要確保緩沖區在合適的位置創建,并能夠供后續的調試操作使用。確保調試系統能夠在需要的時候訪問和寫入該緩沖區,以便正確地顯示調試數據。
總結:
核心目標是通過創建全屏緩沖區,并正確設置其參數,確保調試系統能夠獨立地進行繪制操作。這樣一來,調試功能就能與游戲的渲染系統有效分離,避免不必要的耦合,提高代碼的可維護性和清晰度。
使用調試器進入 DEBUGGetState
。
當前遇到的問題是 DEBUGGetState
函數出現了問題,原因是它在調用時并沒有正確初始化。這個問題發生的原因是因為 DEBUGStart
函數在代碼中的調用順序,使得在調用 DEBUGGetState
時,調試狀態還沒有被初始化。
解決方案:
- 這個問題實際上是因為在整合和簡化代碼時,調整了調試系統的結構,導致某些代碼的執行順序發生了變化。
- 需要對代碼做輕微的重組,確保在調用
DEBUGGetState
之前,DEBUGStart
已經被正確地初始化并執行。也就是說,確保調試狀態在整個調試流程中被正確設置。
總結:
在整理代碼時,調整了調試系統的結構,導致了初始化順序的問題。通過輕微重組代碼,確保調試狀態的正確初始化,將能夠解決這個問題。
在 game_debug.cpp
中,將 DEBUGGetState
復制到 DEBUGGameFrameEnd
內部。
現在,遇到的問題是 DEBUGGetState
可能沒有被正確初始化。為了處理這個問題,決定將 DEBUGStart
的相關代碼直接放到這個位置。實際上,當前已經不再需要 debug_state
作為一個獨立的功能,因此這部分代碼將被簡化和合并。
解決方案:
- 直接將
DEBUGStart
的內容放到合適的地方,這樣可以避免debug_state
的初始化問題。這樣做的目的是簡化代碼結構,因為debug_state
已經不再需要獨立存在。 - 在執行
DEBUGStart
之前,不需要再斷言它已經初始化,因為知道它在當前的代碼結構下會被直接初始化。
總結:
通過將 DEBUGStart
的初始化代碼直接放置在調用的地方,避免了 debug_state
不需要的復雜性和初始化問題。簡化了代碼流程,使其更加清晰和直接。
運行游戲,發現整體狀態有所改善。
現在,整體代碼結構有了顯著的改善,之前的問題已經解決。具體來說,原本的 bug 是在于可執行文件在每次調用和顯示之間可能會重新加載,這種情況雖然不會影響游戲本身,但會影響調試代碼的穩定性。通過調整,確保了可執行文件在調試過程中不會在兩次調用之間被重新加載,從而避免了這個 bug。雖然這是一個調試中的小問題,但修復它非常重要,因為如果調試代碼中存在問題,就會減少開發者使用調試工具的頻率,而調試工具的目的就是幫助發現并解決難以察覺的 bug。
關鍵改動:
- 確保調試代碼不受影響:通過改變代碼結構,避免了調試系統直接與游戲代碼交互,從而保持了調試代碼的獨立性和清晰性。現在,游戲代碼只負責寫入調試緩沖區,而不會直接調用調試系統。
- 簡化調試流程:減少了調試過程中潛在的復雜性,使得開發者可以更輕松地使用調試系統,而不會因為調試系統本身的問題而影響使用頻率。
雖然這個更改解決了當前的一個問題,但結構上仍然有很多可以改進的地方,例如尚未采用滾動緩沖區來處理幀數據的相關問題。這個可以在未來進一步優化。
總體而言,這次調整是一次成功的改進,確保了調試過程的順暢和可靠,進一步提高了調試代碼的可用性。
關閉 GAME_INTERNAL
選項。
現在,我將 game_internal
設置為零,并希望查看如果將其關閉會發生什么情況。我也不太確定 game_profile
的作用,但目前似乎不太重要。總的來說,我的計劃是將 game_internal
關閉,看看效果。
在 game_platform.h
和 win32_game.cpp
中,添加 GAME_INTERNAL
相關的條件編譯判斷。
現在,計劃是去掉那些不再使用或以后不會再使用的部分。首先是 game_internal
,因為實際上我們并沒有真正使用它。接下來,查看了一些內部的文件和內存管理功能,比如 debug_free_file_memory
和 get_process_state
,這些都是內部功能,也應該去掉。然后是全局調試表 global_debug_table
,這個表格存在于平臺的頭文件中。對于這個表格,處理方式就是確保這些內容在關閉 game_internal
時也能被移除。
所以,實際上,只要確保在關閉 game_internal
時,相關的調試和內存管理功能都能按需去除就行。如果 game_internal
被關閉,那么就不需要這些調試和內存管理功能了。
在 game_platform.h
中,移除 GAME_PROFILE
,改為使用 GAME_INTERNAL
。
現在,計劃是去掉那些不再使用或以后不會再使用的部分。首先是 hand_internal
,因為實際上我們并沒有真正使用它。接下來,查看了一些內部的文件和內存管理功能,比如 debug_free_file_memory
和 get_process_state
,這些都是內部功能,也應該去掉。然后是全局調試表 global_debug_table
,這個表格存在于平臺的頭文件中。對于這個表格,處理方式就是確保這些內容在關閉 game_internal
時也能被移除。
所以,實際上,只要確保在關閉 game_internal
時,相關的調試和內存管理功能都能按需去除就行。如果 game_internal
被關閉,那么就不需要這些調試和內存管理功能了。
現在,考慮到代碼的簡化,決定去掉 game_profile
,只保留 game_internal
,因為 game_profile
看起來可能有點多余,變成了太多開關的組合,增加了復雜性。因此,決定只使用 game_internal
,并保持 made
和 made_32
作為平臺配置的唯一開關。
這樣,game_internal
控制是否啟用調試輸出,而 made
控制是否啟用斷言。這個結構現在看起來更簡潔了。
接下來,修改了一些邏輯。對于全局調試表 (global_debug_table
),只有在 game_internal
開啟的情況下才會使用。在卸載調試表的代碼中,也只有在 game_internal
啟用時才會執行。這樣,所有和調試相關的功能都可以根據是否啟用 game_internal
來動態編譯和移除。
最后,雖然有一些地方代碼略顯雜亂,但總體來說,現在能夠更加干凈地編譯代碼,避免不必要的調試功能和開關,同時保留必要的調試功能,整體結構更清晰。
運行游戲,成功執行。
現在,檢查了一下代碼,確認它能夠正常運行,一切都很好。接下來,計劃確保能夠重新啟用所有功能,確保在需要的時候調試代碼能夠再次啟用。
切換 game_INTERNAL
選項后,游戲仍然能正常運行。
現在檢查了一下代碼,確保調試功能能夠正常關閉并且游戲能按常規運行,結果一切正常。對于之前的設計,感覺將“game profile”和“game internal”放在一起可能有些過于復雜,雖然現在它能正常工作,但未來可以進一步簡化這些配置,減少代碼中的復雜度。盡管如此,當前的解決方案還是可以接受的。
接下來計劃繼續進行改進,雖然已經接近完成,但仍然有一些優化的空間,可以將一些調試的部分進一步簡化。接下來可以繼續關注如何處理一些剩余的調整和未來可能的優化,確保在保持功能完整的同時,代碼結構盡量簡潔。
思考接下來的開發方向。
目前的系統已經實現了一些基本的功能,包括調試菜單和調試界面的設置,接下來打算進一步改進調試功能,加入更多的交互界面。例如,可以通過右鍵啟動“離合模式”,在這個模式下可以訪問一些功能,停止數據記錄并進行進一步的分析。
具體來說,考慮在調試界面中加入一些交互按鈕,比如“暫停”按鈕,用于暫停正在運行的游戲,或者其他類似的操作。同時,也計劃設計一些調試界面分組的功能,當進入“離合模式”時,這些分組會展開,退出模式后則會重新收起,這樣可以在調試時靈活控制顯示的內容,讓界面更加簡潔和易用。
在 game_debug.h
中,將 debug_variable_group
的內容移到 debug_variable_reference
里。
為了實現所想要的改進,計劃解決當前調試系統中的一個問題,即在復制調試層次結構時,狀態和數據并沒有被完整復制,只是作為引用存在。這個問題雖然看似簡單,但實際上需要對調試變量的結構進行一些調整。
目前的調試系統中,調試變量是存儲具體數據的,而調試變量組(debug variable group)則負責管理這些數據,包括指向子元素的指針。現在的目標是將調試變量引用(debug variable reference)的概念提升到調試變量組的層次上,也就是說,把所有關于層次結構的元數據移到調試變量引用中。這樣,調試變量本身只包含數據,沒有層次結構的信息。調試變量組作為管理結構的概念,將被移除,不再直接存在于變量內部。
通過這樣的修改,調試系統的結構會更加清晰和簡潔,同時也能更方便地復制和操作調試數據和狀態。
在 game_debug.h
中,考慮是否要對變量進行更細致的拆分。
為了更好地組織和處理調試變量,需要對現有結構進行一些調整,尤其是要將調試變量和其顯示屬性分開。當前的調試變量(例如調試位圖顯示)包含了兩類信息:一類是實際的顯示數據(如位圖的大小、是否顯示透明度等),另一類是調試變量的標識符(即具體被調試的內容)。這兩類信息目前是合并在一起的,但實際上它們應當分開處理,以便更清晰地管理。
計劃的思路是將調試變量和它的屬性(例如顯示屬性)分開成兩個不同的層次。調試變量本身只包含數據,例如位圖的標識符;而顯示屬性(如位圖的大小、透明度等)則作為一個獨立的部分存在。這種做法有助于使調試系統更加模塊化和清晰,從而提高其可擴展性和靈活性。
通過這樣的結構調整,調試變量和其顯示屬性將各自獨立,避免了信息的混合,使得系統在管理和擴展時更加高效和易于維護。
在 game_debug.h
中,將 debug_variable_
結構體重命名為 debug_tree
和 debug_tree_entry
。
為了進一步優化調試系統的結構,可以考慮將現有的調試變量和調試層級進一步簡化,避免過度復雜的結構。當前的系統中,調試變量和它們的顯示屬性、層級結構等有些過于分散,考慮將它們統一到一個清晰的層次中,以簡化管理和使用。
具體來說,可以引入類似“調試層級”或“調試樹”的概念,將調試信息按樹形結構組織起來,而不是像現在這樣有多個重復的元素。這不僅能讓結構更加簡潔,而且也便于后期擴展和維護。比如,調試變量的層級關系可以通過簡化的方式來表達,避免不必要的重復,使得系統的可讀性和管理更加直觀。
這些調整可能會使代碼更為清晰,也有助于后續的維護和升級。同時,也可以考慮適當合并一些冗余的結構,以減少不必要的復雜性,確保系統的高效性。
在 game_debug.h
中,新增 debug_tree_entry_group
結構體。
在設計一個調試樹(debug tree)時,可以將每個節點設計為一個具有子節點的結構體,這樣形成層級關系。這些節點不僅存儲調試變量,還可以包含其他相關的數據,以便在調試過程中提供更豐富的信息。每個節點可以有指向下一個節點的指針,允許樹形結構的擴展。
例如,樹中的每個節點可以表示一個“組”,這個組內包含多個變量或調試項目。每個組都可能有一個“擴展”標記,用于表示是否展開這個組,允許靈活的管理顯示內容。同時,節點還可以包含一個“聯合體”數據結構,這樣可以根據需要存儲不同類型的調試數據,確保每個節點能夠靈活地包含不同類型的調試信息。
這種設計將數據的組織方式從原先的簡單層級提升到一個更靈活、更高效的結構,使得調試信息的管理和訪問更加直觀和高效。通過這種方式,調試系統不僅能更好地應對不同的數據類型,還能夠通過動態展開和收縮的方式優化調試視圖,提高用戶的調試體驗。
在 game_debug.h
中,重寫 debug_tree
。
在調試樹的設計中,考慮將調試樹的結構簡化和調整。調試樹應該包含字典條目來表示組,而這些組可以作為樹的節點。每個節點可能包含一些基本的調試信息,如組的名稱、類型等。調試樹結構中的每個節點都可以包含一個指向下一個節點的指針,這樣便于構建樹形結構。
考慮到簡化結構的需求,節點的組織方式應去掉一些不必要的元素,例如不需要“下一節點”和“前一節點”的指針,因為每個節點已經可以通過樹的結構自然連接起來。通過這種方式,可以減少不必要的冗余,確保結構的簡潔和高效。
在考慮是否將樹放在調試信息中時,意識到可能沒有太大必要在中間嵌入樹結構。實際開發中,調試樹應更簡單有效,避免不必要的復雜化。重新思考后,決定去掉樹的中間插入部分,專注于一個更直觀和易于管理的結構。
在 game_debug.h
中,新增 debug_tree_entry_window
結構體。
在設計調試系統時,計劃引入一種集成化的結構,包括調試樹(debug tree)和一些簡單的“窗口”類型。這些“窗口”可以用來表示需要顯示或操作的調試數據,用戶可以輕松使用這些功能,而不需要額外的定義或復雜配置。例如,對于那些只需要簡單調試功能的用戶,可以直接使用這些預定義的調試樹條目,而不必自己去定義更復雜的結構。
此外,設計中考慮到將“窗口”和“組”作為調試系統的核心組成部分,簡化了變量的管理和結構。為了避免冗余,計劃移除一些不必要的設置,尤其是那些在當前版本中沒有實際內容的配置項。這些空的配置項會作為占位符保留,以便未來有需要時能夠添加內容。
整體目標是使系統更簡潔、更易于使用,同時保留足夠的靈活性,以便在將來能夠擴展更多的功能。
在 game_debug.h
中,新增 debug_tree_entry_type
結構體。
在設計調試系統時,計劃引入兩種類型的調試變量:一種是基礎的調試變量類型,另一種是樹形結構條目類型。樹形結構條目類型(debug tree entry type)將是基于當前的調試條目類型,并將擴展為“變量”(variable)、“組”(group)和“窗口”(window)等具體類別。
這些類型將用于管理調試樹中的條目,例如,基本的調試變量和樹結構中的分組、窗口等。所有這些類型的條目將被統一歸類為調試樹條目(debug tree entries),并在系統中進行相應的管理。
這樣,系統的設計將更加簡潔和清晰,能夠有效地將不同類型的調試信息進行分類和組織,使得在調試過程中,用戶能夠更容易地進行操作和查看相關信息。
回顧這些改動。
現在,可以看到所做的調整非常簡單,只是稍微調整了一下結構。核心思路是將調試信息分為兩套并行的信息集:一套是調試變量,這些變量可以被檢視并進行操作。調試變量可以是由運行的應用程序動態創建,或者根據需求進行創建。
在這些調試變量之上,我們可以加上一層用于查看這些信息的結構,這樣就可以在不同的樹形結構中查看同一組數據,但每個樹的視圖方式可以不同。這樣,多個樹形結構可以同時查看相同的數據,但它們的呈現方式和視角可以不同,這正是目標之一。
此外,在實現時,我們將清理掉所有的編譯器生成代碼,最終的實現會是一個深拷貝的過程,將這些調試變量及其信息復制到新的位置,這就是所需要的操作。
在 game_debug.h
和 game_debug_variables.h
中,清理編譯錯誤。
現在,開始進行相應的操作,可能需要一些時間。首先,我們已經定義了一個雙重樹的結構,并且需要提前聲明這個結構,因為它是用來創建一個內部的鏈表的。
接著,我們的變量層級(之前是調試變量層級)現在被簡化為“調試樹”,因為原來的名稱有些復雜,拼寫起來不太方便。現在,這個名稱更加簡潔清晰,所以用“調試樹”來代替。
在定義這些結構時,變量引用實際上是一個文檔性的概念,并不是直接的條目,它更準確地應該是“調試樹條目”。根組和調試樹的樹哨兵都已經定義好,現在我們需要調整相應的結構,確保它們的定義更加符合實際的使用。
目前的工作重點是創建兩種主要的結構:一種是實際的調試變量,它們包含了需要檢視的數據;另一種是創建調試樹的層級結構,用于組織和查看這些調試變量。
考慮采用基于緩存的系統(caching-centric system)。
雖然手動創建層級結構有些麻煩,但目前并沒有太多辦法可以避免這個過程。盡管如此,還是有一些方式可以稍微繞過這個問題。比如,可以使用一種緩存系統,來避免每次都直接處理這些數據。
實際上,可以考慮使用這種基于緩存的系統,雖然它看起來與現在的結構相似,但它的解釋器是解耦的。通過這種方式,可以簡化管理數據的流程,避免直接維護每個層級。這種做法不僅在實現上更有趣,而且從功能上也更強大。所以,雖然現在的方式可以手動創建層級結構,但使用緩存系統可能會讓這一過程變得更加高效和有趣。
具體的做法是,我們可以創建一個樹結構來存儲數據,并且將調試變量與樹的層級結構分開。樹本身保存了必要的層級信息,而調試變量則包含了每個變量的實際數據。這樣,調試變量的不同組別可以分別管理,但其他的屬性(如顯示參數)則只是針對每個樹結構的具體數據。
在這種方案中,每個調試變量將會從一個表格中查找它的狀態,而不是一直保存和維護這些狀態。這個表格會告訴調試變量如何進行格式化處理,確保在顯示時能夠正確呈現每個變量的實際數據。這種方法簡化了變量的管理,并且使得調試過程中不需要每次都手動處理每個變量的顯示屬性。
在 game_debug.h
和 game_debug_variables.h
中,開始實現基于緩存的系統。
在這個方案中,目標是通過簡化和改進調試變量的管理,避免過于復雜的樹結構層級和變量引用,改用更加簡潔和靈活的方式。具體來說,首先將調試變量與視圖的關聯進行簡化,使用“調試視圖”來表示顯示變量的方式,而不再直接依賴復雜的樹形結構。
這種做法采用了一個緩存機制,通過哈希表來存儲調試視圖,而不再依賴每次都重新構建復雜的樹形結構。每個調試變量都會被存儲為一個基本的視圖類型,比如“行內塊”,這個視圖類型可以包含顯示相關的信息,比如大小、狀態等。調試視圖會通過哈希表與調試變量關聯,從而確保可以快速檢索到對應的視圖。
調試變量本身被看作是“樹”的一種抽象,雖然樹形結構的部分仍然存在,但更多的是通過變量列表和哈希表來處理。調試變量列表可以是一個包含多個變量的數組,變量在這個數組中被添加或移除。在實現上,通過引入調試變量列表,可以讓調試工具更靈活地管理和呈現多個變量。
此外,調試視圖不再包含復雜的層級信息,而是簡單地依賴哈希表來檢索視圖,避免了不必要的復雜結構。通過這種方式,調試過程變得更加高效,變量的管理變得更加直接和清晰。調試視圖和調試變量之間的關系由哈希表來處理,這樣可以避免創建過多冗余的結構。
這種設計的優點是,調試工具變得更加靈活,可以根據不同的需求來展示變量,且不需要過多關注變量引用的問題。所有的變量都被當作普通變量來處理,簡化了處理流程。對于復雜的調試信息,所有的數據都可以通過哈希表進行存儲和快速訪問。
總結來說,這個系統通過引入更靈活的緩存機制,減少了對樹形結構的依賴,并通過哈希表來管理調試視圖與變量的映射。這樣既保證了調試信息的有效存儲,也提高了代碼的可維護性和靈活性。
暫時暫停調試,并關閉 GAME_INTERNAL
選項。
目前,我們暫時停止了變量系統的重構,以確保當前系統仍然處于可用狀態。在此過程中,我們做了一些重要的調整,以優化調試變量的管理方式,并改進變量的組織結構,使其更加合理和易擴展。
我們最初的想法是通過樹狀結構管理變量,但最終決定簡化這個思路,轉而采用更輕量級的方法。我們將調試變量視為一個獨立的調試視圖(Debug View),而不再讓它與樹狀結構過于耦合。所有調試變量都會被存入哈希表,并根據具體的需求決定如何呈現,例如是否作為一個嵌入式塊(inline block)或者其他類型的顯示方式。
在這個新方法下,我們取消了變量引用(Variable References)的概念,所有變量都統一管理,不再區分“變量”與“變量引用”。所有的調試變量都會存入一個數組,這個數組的大小可以根據需求調整。在存儲結構方面,我們創建了 debug_variable_list
(或 var_list
),用來存儲一組調試變量,并提供訪問和管理的方式。每個 debug_variable_list
僅存儲變量,不再額外存儲引用信息,從而減少了復雜性。
在實現這一改動的過程中,我們調整了 debug_variable_group
及其相關邏輯。原來的 debug_variable_ref
和 debug_variable_unreferenced
邏輯被完全移除,現在所有變量直接存入 debug_variable_list
,并在需要時通過哈希表查找匹配的調試視圖(Debug View)。當創建新的調試變量時,我們會在 debug_variable_list
中分配新的存儲空間,并將變量存入其中。
此外,我們還改進了變量的展開狀態(expanded state)管理方式。原本變量的展開狀態是直接存儲在 debug_tree_entry
中,但在新的實現方式下,我們使用 debug_view_collapsible
組件來管理展開狀態,并支持不同的展開模式,例如“始終展開”(expanded always)或“根據不同視圖展開”(expanded in alt view)。
在數據結構方面,我們在 debug_state
中添加了 debug_view_hash
,用于存儲所有 debug_view
的哈希表。這樣,每當需要獲取某個變量的視圖信息時,我們可以通過哈希表快速查找對應的 debug_view
,而不需要依賴樹狀結構進行遍歷。
目前,我們已基本完成變量系統的改造工作,但仍有部分代碼需要進一步整理。例如,我們需要優化 debug_variable_group
的創建方式,以確保在 begin_variable_group
之后正確地分配和存儲變量組數據。同時,我們也需要考慮是否需要更智能的內存管理方式,比如使用可擴展數組(expandable array)來存儲變量組,而不是手動設置固定大小的數組。
最后,為了確保系統穩定,我們暫時禁用了 debug_variable_group_internal
,并保留了原有的變量管理邏輯,以防止影響現有的功能。待后續進一步完善新系統后,我們將徹底切換到新的變量管理方式。新的方式不僅減少了冗余代碼,提高了變量管理的清晰度,還使得調試變量的組織方式更加靈活,支持不同的視圖模式和更好的參數管理。
接下來,我們將在下一次工作時完成變量系統的遷移工作,并優化變量存儲方式,以便支持更合理的組織方式和更清晰的參數存儲。
現在調試代碼越來越多了,是否會創建一個調試調試系統(debug-debug system)來調試調試代碼?
目前,隨著調試代碼(debug code)的增加,整個調試系統的規模也在變大。在創建調試系統時,核心內容主要圍繞調試代碼本身展開。與此同時,演示代碼(demo code)的能力也得到了增強,甚至可以自我管理和調配資源,這使得整個系統在調試和演示時更加靈活。
實際上,我們已經具備了這種能力,并且在整個過程中都在利用這一特性。例如,在運行演示系統的同時,我們也在對調試系統進行計時(timing),這使得調試和演示能夠協同工作,而不會彼此干擾。這種方式不僅提高了調試的效率,也使得調試過程更加直觀。
在整個開發過程中,我們一直在使用這種方式,使得調試系統和演示系統能夠無縫結合。這樣,我們可以在測試功能的同時,對調試代碼的運行情況進行監測,從而確保系統的穩定性和性能表現。這種方法的優點在于,它讓調試系統能夠在演示過程中自動適配,從而減少手動調整的需求,并優化整體的開發體驗。
你如何看待預取緩存指令(pre-fetch cache instructions)?它們是否適用于通用編程,以獲取最大性能?
在討論特權緩存指令(privileged cache instructions)是否適用于通用計算以獲得最大性能時,我們需要考慮其實際作用以及適用場景。
預取指令(prefetch instruction)的作用
預取指令的本質就是提前告知 CPU 即將訪問的內存區域,以便 CPU 可以提前加載數據,減少因數據未就緒而導致的停滯(stall)。如果代碼中存在需要訪問某塊內存的情況,而該訪問可能會導致處理器停滯(例如,在一個循環中反復訪問某些數據),那么可以在執行這些訪問之前很多個周期(如 300 個周期)提前插入預取指令,這樣處理器就有足夠的時間去獲取相應的數據,從而減少等待時間,提高整體執行效率。
預取指令的適用條件
-
需要提前知道訪問的內存地址
預取指令的有效性完全取決于是否能提前預知即將訪問的內存地址。如果代碼執行過程中無法提前知道哪些數據將被訪問,預取指令就無法發揮作用。因此,它最適用于那些訪問模式可預測的場景,比如循環結構或結構化的數據訪問。 -
不能達到最大內存帶寬
預取指令的作用是減少緩存未命中(cache miss)造成的停滯,而它的前提是系統尚未達到最大內存帶寬。如果 CPU 已經完全占滿了可用的內存帶寬(full memory bandwidth),那么額外的預取指令不會提高性能,因為此時已經達到了吞吐量極限。換句話說,預取指令的作用是優化緩存命中率,而不是增加內存吞吐量。 -
僅在緩存未完全飽和的情況下有效
預取指令的另一個限制在于,它僅在緩存訪問導致停滯且內存帶寬未被完全利用時才有效。如果處理器已經完全利用了帶寬并達到了最大吞吐量,預取指令不會帶來額外的性能提升,因為系統本身已經達到了硬件能力的上限。
總結
預取指令的作用是減少緩存未命中導致的處理器停滯,從而提升程序執行效率。它在以下情況下最為有效:
- 代碼可以提前知道即將訪問的內存地址;
- 內存帶寬尚未被完全占用;
- 存在因緩存未命中導致的 CPU 停滯。
如果無法滿足這些條件,預取指令的效果可能有限,甚至可能導致額外的指令開銷。因此,在具體應用時,需要根據程序的訪問模式、緩存命中率和內存帶寬利用情況進行權衡和優化。
目標系統的架構是否會影響你設計調試系統的方式?
在設計調試系統時,目標系統的架構通常不會對設計產生重大影響。調試系統的基本結構通常是通用的,無論運行環境如何。不過,在某些特定情況下,例如目標系統的性能較低,可能需要做出一些調整。
如果目標系統是一個性能較弱的平臺,例如 Nintendo DS 這樣的設備,那么可能不會使用調試覆蓋(debug overlays),因為這會占用系統資源。相反,調試信息通常會被導出到一個 調試端口,并由另一臺更強的設備捕獲和顯示調試數據。
而在運行于高性能設備(如 PC 或強大的主機)時,調試系統通常可以直接在本機運行,而不需要額外的設備來承擔調試任務。因此,在 弱性能系統 上,調試系統的設計可能會傾向于將調試工作交給另一臺設備,而在高性能設備上則不會有太大限制,調試系統可以直接集成并運行在同一設備上。
你對 《無人深空》(No Man’s Sky) 有什么看法?我現在完全無法想象它的工作原理,學習你的開發方式后,我會對這類游戲的實現有更深入的理解嗎?
《No Man’s Sky》的核心技術是程序化世界生成,其工作方式與《Minecraft》類似,都是基于按需生成的概念。當玩家移動到新的區域時,系統會請求該區域的世界數據,并根據所需的**細節層級(Level of Detail, LOD)**來生成對應的內容。
當某個區域的細節需求較低時,系統返回的只是一個粗略的版本,而當玩家靠近并需要更高的細節時,系統會進行進一步的細節填充并動態生成內容。這種技術并不是魔法,而是程序化生成技術的自然應用。在現代硬件的支持下,這種方法可以實現極高的細節度,從而創造出廣闊而豐富的游戲世界。
這種技術并不是全新的概念,類似的方式在過去的項目中也曾被使用過。區別在于,現在的計算能力已經足夠強大,可以在更高的精度上執行這些生成任務,從而創造更為龐大和復雜的世界。
當你發布源碼后,我們能創建自己的boss、世界等內容嗎?
如果擁有源代碼,那么理論上可以進行任何修改和擴展。可以添加新的Boss、世界、機制,或者進行任何形式的改動,只要有足夠的編程能力,就沒有任何限制。源代碼的開放意味著可以完全掌控游戲的邏輯和功能,因此可以自由地開發和調整各種內容,按照自己的想法來塑造游戲體驗。
你會很快回到游戲本體的開發嗎?
目前的編程學習方向并不是專注于展示某個具體的內容,而是更側重于編程教育本身。因此,不會因為某些人希望快速看到特定內容的編寫就匆忙推進,而是會花足夠的時間深入學習各種知識點。由于所有內容都可以按需回放,如果對某個環節不感興趣,可以隨時跳過,等到后續感興趣的部分再回來觀看。
游戲玩法(Gameplay)的編碼實際上是編程中最無聊的部分之一,通常相當基礎,很多時候類似于腳本編寫。例如,移動一個對象、改變其位置、讓其旋轉等,基本上是簡單的數值調整和邏輯處理,沒有什么特別復雜的內容。因此,與其關注這類代碼,不如專注于更具挑戰性的部分,比如構建高效的 UI 系統,這樣的內容涉及優化和架構設計,真正具有技術難度,也更值得學習。
相比之下,游戲玩法代碼相對簡單,通常不會涉及太多深奧的邏輯。因此,并不會特意加快進度去進入游戲玩法代碼的編寫,因為相比之下,系統架構、優化、工具開發等部分才是更具挑戰性和價值的內容,值得投入更多時間去研究和掌握。
樹莓派(Raspberry Pi)或其他 ARM 架構的 PC 能否運行 game Hero?如果使用這樣的系統,你會考慮在另一臺機器上進行調試嗎?
如果使用樹莓派(Raspberry Pi)或其他基于 ARM 架構的桌面 PC 來運行當前的項目,理論上是可行的,但性能上可能會受到一定限制。
樹莓派的新版本相比最早的版本擁有更強的處理器,尤其是樹莓派 2 及之后的版本,引入了四核架構,性能有所提升。因此,能否流暢運行取決于具體的硬件配置。如果是較早的樹莓派版本,想要完全通過軟件渲染方式運行游戲,幾乎是不可能的,因為其計算能力遠遠不及 x86 處理器,必須依賴 GPU 進行加速。
至于較新的樹莓派,或許可以運行,但可能無法達到 1080p 分辨率,需要降低畫質或分辨率來保證流暢度。不過,由于沒有在樹莓派 2 或更新的設備上進行過測試,因此暫時無法給出明確的結論,不排除其具備一定的運行能力的可能性。
我指的是游戲內部,是否可以在調試菜單里切換優化選項?
可以在調試菜單中添加一個選項來切換游戲的某些特定狀態,這是完全可行的,并且實現起來非常簡單。
具體來說,可以使用程序優化技巧來完成這個功能。這樣,就可以把這個狀態變量作為標準變量,直接放入 DEBUG_VARIABLE_LISTING
文件中進行管理。這種方式能夠簡化變量的存取,同時也能確保它可以輕松地從調試菜單中進行切換。
因此,接下來的優化方向應該是:
- 調整代碼,確保變量能夠被調試系統識別并管理。
- 在調試菜單中添加對應的選項,允許實時開關某個特定狀態。
- 驗證實現效果,確保切換功能能夠正確應用于游戲運行時。
接下來就可以著手實現這個調整。
你覺得可視化編程語言(GUI Visual Scripting)用于游戲腳本開發是否是個不錯的主意?比如 Unreal 的藍圖(Blueprint)系統?
對可視化腳本的看法并不十分贊成,因為可視化腳本通常會變得非常復雜,充滿了大量的節點,看起來像是一個巨大的混亂。對于程序員來說,這種方式并不特別有效,因為它最終可能只需要幾行代碼就能實現同樣的功能。
從個人經驗來看,可視化腳本并沒有帶來太多好處。它的存在似乎是為了替代傳統編程,但往往沒有顯著提高效率,反而可能讓流程變得更加繁瑣,尤其是當涉及到復雜的邏輯時。對比起用代碼直接解決問題,這種節點化的工作流顯得非常冗長且不夠直觀。
簡而言之,可視化腳本的優勢并不明顯,大多數情況下,直接用幾行代碼處理問題會更加高效清晰。
黑板討論:可視化腳本(Visual Scripting)。
對可視化腳本語言的看法非常不喜歡。相比起傳統的文本編程,可視化腳本在很多情況下顯得非常笨重和難以理解。舉個例子,當想要實現一些簡單的功能,比如設置一個角色的健康值,可以直接寫出類似“health = aimAmount * 2 / 5
”這樣的代碼,一行就能完成,既簡潔又容易理解。而且,如果需要,還可以輕松在這行代碼上方添加注釋,解釋它的作用,便于后續理解和維護。
但使用可視化腳本時,這個簡單的邏輯就變得異常復雜。可能需要創建許多節點,比如一個“驅動節點”來設置值,再用另一個“乘法節點”將兩個數相乘,然后再用“加法節點”進行加法操作,最后再進行除法處理。每一步都需要一個獨立的節點,而且這些節點之間需要通過管道連接。結果是,原本一個很簡單的計算邏輯,變成了一個充滿節點和連接的混亂圖,讓人難以理解。每個節點背后都代表著一個看似簡單卻需要過多步驟的操作,最終讓人覺得這一切變得極為冗長和混亂。
對于這種方式,盡管它可能有一定的使用場景,但無法忍受這種復雜的、零散的方式來做一些本可以輕松解決的任務。直接用代碼寫出來會更加簡潔清晰,且更容易理解。雖然可能存在一些優勢,但對自己來說,可視化腳本總是顯得不直觀,甚至讓人感到沮喪。
你能否只整理上一幀的數據,而不是一次性處理所有幀的數據?
目前的問題在于,雖然每一幀都可以處理最新的數據,但實際上無法無限制地這樣做。因為我們并沒有使用滾動緩沖區,最終數據空間會被填滿,一旦寫入的數據沒有地方存儲,就需要清空緩沖區,然后重新開始處理舊的數據。因此,不能一直僅處理最新的數據。
解決方法就是實現一個循環緩沖區,這樣就可以在數據寫滿后,自動覆蓋舊數據,而不需要清空整個緩沖區。代碼已經在增量處理方面能夠正常工作,只是缺少了這個“滾動”的功能,導致在某些時刻需要清空數據并重新開始處理。
總的來說,問題的關鍵在于沒有循環緩沖區,而一旦實現這個功能,代碼就能更高效地工作。當前的隊列處理看起來已經完成,可以繼續推進。