回顧并為今天的工作做鋪墊
隨著時間的推移,我們的分析器(profiler)變得越來越強大。我通常會問大家是否記得我們要做什么,今天我們要做的似乎是按鈕相關的功能。
今天的目標是實現按鈕功能。我們從昨天留下的地方繼續,今天我們繼續完善我們的分析器,之前我們已經使分析器看起來非常酷,并且做了一些相當不錯的功能。例如,我們現在可以查看游戲中發生的特定事件,并暫停調試信息,查看每一幀的詳細分析。
目前,最棒的功能之一是,我們可以暫停分析器并對其進行深入調查,這使我們能夠查看特定事件的詳細信息。然而,目前有一些問題。一個問題是,當我深入分析某個事件時,我無法返回上一級。也就是說,一旦我深入某個數據,我就不能再回到之前查看的內容,無法返回上一級,不能調整任何內容。
這種情況的原因是分析器中缺少控制按鈕,我們沒有辦法在分析器的控制面板中加入讓我們能夠控制這些操作的按鈕。因此,今天我們需要解決這個問題,給分析器加入控制按鈕。
game_debug.cpp:切換到使用完整的內存區域
目前,我們正在對調試器進行一些調整。在之前的測試中,我們曾使用8MB的內存來測試調試后備緩沖區的回收機制,但這不是我們想要的,原因是我們需要足夠的內存來存儲更多的幀數據。現在,我們已經恢復到足夠的內存,并且有83MB的剩余空間。實際上,我們還可以將內存稍微減少一些,但目前的空間足夠了。這樣,我們可以保留256幀數據,同時保證調試系統有足夠的空間。
接下來,目標是添加更多的調試控制功能。例如,除了播放和暫停按鈕,還需要添加一些可以更好控制分析器視圖的按鈕。比如,可能需要實現縮放或平移的功能,另外還需要添加返回按鈕,便于返回到之前的狀態。此外,還可以恢復之前創建的調試百分比視圖,現在沒有辦法重新顯示這些視圖。總之,缺乏互動的方式讓目前無法進行很多操作,因此今天的重點是添加這些按鈕,以便可以與調試系統進行交互。
game_debug.cpp:重構DEBUGDrawElement,增加了切換元素類型的能力
在當前的調試系統中,我們希望能夠更加靈活地控制和顯示數據。我們考慮過如何將不同的元素與控件(比如按鈕)結合起來,以便用戶能夠與這些元素進行交互。例如,在我們的界面代碼中,已經可以簡單地添加像“幀滑塊”這樣的控件,只需將其插入調試系統中,它就能夠被渲染和使用。接下來,我們的目標是將這些控件功能添加到現有的元素中,確保它們能夠在調試界面中發揮作用。
具體來說,我們需要為“線程區間圖”這種元素添加交互控件。當前的想法是,讓這些圖表能夠根據用戶的需求動態切換顯示的類型,比如切換到“線程分析”或“調試圖表”模式。為此,我們考慮通過一種可變類型的方式,使得用戶可以根據需求調整圖表的顯示方式。通過這種方式,不僅能使圖表的類型可變,而且用戶在查看不同的調試數據時也能夠更方便地進行切換。
在調試元素的創建過程中,我們已經有一個機制來決定每個元素的類型。最初,我們是通過事件來確定元素的類型,但為了更靈活的處理,考慮將類型信息直接放到調試元素中,這樣當需要切換類型時,可以直接修改該字段,而不需要重新創建元素。這樣做不僅簡化了代碼,也讓我們能夠更加方便地動態調整每個元素的顯示類型。
此外,還涉及到如何處理與元素類型相關的數據,如圖表的位圖。我們決定將元素的位圖從事件中分離出來,直接通過元素的屬性來獲取,這樣做可以減少不必要的冗余,并簡化代碼的維護。通過這種方法,我們可以確保每個元素都能夠正確地展示其對應的數據。
接下來,我們需要實現一個按鈕控件,用于控制這些圖表的顯示類型。這個按鈕將允許用戶在不同的視圖模式之間切換。為了實現這一點,我們需要確保能夠動態改變圖表的類型并渲染出不同的圖表內容。
總結來說,目標是通過將元素的類型設置為動態可變,結合按鈕等交互控件,來提升調試系統的交互性和靈活性。這樣不僅可以提高用戶在調試時的操作效率,還能使調試系統更加易用和直觀。
運行游戲,查看沒有變化
我們希望一切保持不變,目前確實一切都和之前一樣,這很好。整體狀態良好,一切正常。我們不太記得之前的具體位置,但最終找到了我們需要的實體。這讓我們確認了一切都在原位,沒有發生改變。
點擊simulation中的entity會觸發斷言
game_debug.cpp:引入NullEvent,以確保在數據為空時仍然打印事件
我們覺得這有點有趣。我們目前只在幀中實際存在某個特定內容時才進行顯示,但也許可以在做這方面改進的時候順便優化這一點。因為我們現在的做法是只有在有數據的情況下才顯示,這雖然減少了閃爍現象,但也帶來了限制。我們其實可以始終顯示這部分內容,只需確保總是有一個事件存在即可。
比如我們可以引入一個類似“調試事件”或“空事件”的機制,這樣即使在某些幀上沒有真實事件,也可以渲染一個默認事件,使顯示保持一致。即使這個事件什么都沒有,但至少可以統一顯示邏輯。
此外,現在我們的事件可能橫跨多個幀,我們也可以在這些事件上繪制圖表或實現其他更復雜的功能。雖然這屬于另一個話題,但說明我們的系統已經具備更強的擴展性。
如果我們添加一個“無事件”的占位事件,我們可以填充事件的名稱字段,比如將之前清除掉的事件名重新寫入,填充事件類型字段為當前元素的類型,然后就可以不用管其他字段。這么做的好處是,我們每一幀都可以傳遞一個事件對象,即使那一幀沒有實際事件內容,也能保證邏輯統一和顯示完整。
目前來看,這種做法在所有場景下都不會出問題,所以我們完全可以現在就實現這個機制。
運行游戲,看到更友好的分析器界面
我們運行了一下,現在效果好多了。現在在沒有數據的情況下,會直接打印出零,而不是出現閃爍或者布局變化的問題。這樣處理后,整體顯示更加穩定和統一,我們認為這種方式在一定程度上絕對是更好的。
比如有些幀其實根本沒有任何數據,但現在不會再因為缺失數據而導致界面突變。雖然有點不確定為什么在完全沒有數據的幀上還能輸出一些東西,不過猜測可能是因為那一幀正好還是有一些歸并處理的內容,所以輸出并不為空,這也挺有意思的。
不管怎么說,這種改進確實讓整體體驗更好了,特別是在保持布局一致性和避免視覺干擾方面。我們認為這是更合理的方案。
game_debug.h:添加DebugInteraction_SetElementType
現在我們想嘗試在界面中添加一些按鈕,這些按鈕能夠執行一些有用的操作,特別是和性能分析器相關的功能。我們計劃添加的按鈕主要用于設置元素的類型,也就是能夠通過點擊按鈕來改變當前的元素類型狀態。
我們打算創建一種新的交互方式來實現這些按鈕的功能。雖然目前還沒有完全確定名稱,但大致會是類似“調試交互”、“設置分析圖表”或者“設置元素生命周期”這類操作的名稱。
接下來的目標是設計一個用于按鈕交互的機制。我們希望這些按鈕可以很方便地被堆疊或排列起來,能夠直觀地使用,也便于在界面上組織和擴展。這將有助于我們更靈活地進行分析相關的操作。
game_debug.cpp:勾畫出BeginButtonRow、BooleanButton、EndButtonRow和ActionButton的調用位置
我們現在希望在界面中添加一些按鈕,用于執行與性能分析器相關的有用操作。我們想以簡單直觀的方式將這些按鈕組織成一行,比如定義一個 BeginRow
的布局邏輯,然后在這其中依次放置多個按鈕。
我們設想每個按鈕的定義都包括三個部分:按鈕的名稱、當前是否處于激活狀態,以及點擊后執行的操作。比如我們想要一個名為 “threads” 的按鈕,它的激活狀態由當前元素類型是否為線程區間圖(thread interval graph)決定;點擊這個按鈕后,會執行一個設置元素類型為線程區間圖的交互操作。
同樣的方式可以復用于其他按鈕,例如添加一個名為 “frames” 的按鈕,對應的元素類型為幀條形圖(frame bar graph)。這樣,我們可以非常方便地堆疊和擴展按鈕,每個按鈕邏輯清晰,定義簡潔。
除了切換元素類型之外,我們還希望增加一些導航操作,比如能夠返回到分析圖的根節點。因此我們打算增加一個名為 “root” 的操作按鈕,點擊后可以跳回性能分析圖的最頂層。
我們也設想過增加“上一級”按鈕,用于在分析圖層級中向上跳轉,但這部分有些棘手。當前的結構中沒有明確記錄父節點信息,也就無法可靠地從當前元素回溯到其上一級。更復雜的是,性能分析圖中某個視圖位置可能有多條路徑到達,路徑關系并不唯一,進一步增加了跳轉邏輯的模糊性。
雖然也考慮過以“縮放”方式來代替具體跳轉,即允許用戶縮放進出不同的視圖層級,而不是通過點擊來切換,但目前我們傾向于先實現明確可控的操作按鈕,確保交互簡單可用。
總之,我們先實現這些確定功能的按鈕,確保界面具備基本的切換與返回功能,然后再逐步探索更復雜的交互方式。期間還意外打開了待辦事項列表,之后會再看一下與性能分析渲染有關的部分。
game_debug.cpp:引入ZoomRootInteraction并將此功能提取到SetElementTypeInteraction中
我們在處理性能分析圖時,之前創建過一個交互,用于設置根節點,現在我們打算回到這個部分,把這個邏輯實現得更完整。我們希望實現一個“縮放至根”的功能,這個交互的行為就是將分析圖的根節點設置為 none
,也就是重置為初始狀態。
我們定義了一個交互,比如叫 zoom_root_candidate
,它的作用就是在被觸發時將元素類型設置為 0,表示重置。為了讓這個交互更易復用,我們打算將其提取成一個函數:這個函數接收一個調試 ID 和一個類型,然后返回一個標準的交互結構。這樣,我們可以直接在按鈕定義中調用這個函數,而不需要每次都手動創建交互邏輯,減少重復代碼,提高清晰度。
接下來我們準備完善按鈕的布局系統。當前已有的 begin_element_rectangle
和其他相關邏輯主要用于通用元素,我們希望新增一個機制來專門處理按鈕的布局。我們的目標是讓這些按鈕的交互邏輯和布局方式都能順利傳遞并生效。
想到這里,我們意識到可能需要將布局對象(比如 layout
或者 row
)作為參數傳遞。雖然嚴格意義上不一定非得顯式傳遞“行”這個概念,但只要能一直傳遞 layout
,就已經能滿足大部分需求了。
所以我們會做以下幾件事:
- 把交互創建邏輯封裝成函數,接收所需參數后直接返回完整結構;
- 設計一套新的按鈕布局方式,可以順利將多個按鈕排列成一行;
- 確保交互信息能在點擊按鈕后正確傳入系統邏輯中;
- 最終目標是讓我們能夠通過簡單語句就定義一排按鈕,每個按鈕有名稱、激活判斷邏輯,以及點擊后的動作。
這個改進不僅讓按鈕的管理和定義更加清晰,也為后續添加更多功能按鈕打下了基礎。我們現在專注于基礎交互的完善,之后還可以擴展更復雜的行為。
game_debug.cpp:實現按鈕功能
我們在嘗試實現一個按鈕系統,用于性能分析器中,通過這些按鈕來執行一些有用的操作,比如切換分析圖的顯示類型或重置根節點。我們的目標是設計出一套通用、靈活的按鈕機制,開發者可以很方便地定義按鈕,而不需要自己實現繪制邏輯。
我們首先設想了使用方式,也就是最終我們希望如何調用這些按鈕。我們采用的是一種“逆向工程式”的方法:先寫出理想的調用代碼,再根據這些調用代碼倒推出內部需要的實現細節。這種方式能幫助我們設計出更貼合實際使用場景的系統結構。
為此,我們設計了幾種類型的按鈕,例如:
- 布爾按鈕(Boolean Button):用于切換某種狀態,比如是否顯示線程圖。我們提供一個布爾值用來判斷當前按鈕是否處于激活狀態,并設置一個交互邏輯來切換狀態。
- 操作按鈕(Action Button):用于執行一個具體動作,比如重置分析圖的根節點。
我們考慮了以下幾個核心點來實現這個按鈕系統:
-
布局系統(Layout)
每個按鈕都需要知道自己的尺寸和在 UI 中的位置。我們不打算把這些按鈕視為“自定義渲染”的元素,而是直接在按鈕函數中完成所有繪制與交互處理邏輯。這讓按鈕使用者不需要處理底層渲染細節,只要調用對應函數即可。 -
基本繪制邏輯(Basic Text Element)
我們有一個基礎的繪制文本元素的方法,它可以被復用。我們準備將其抽出,變成一個更獨立的組件,可以被按鈕系統調用,用于繪制按鈕的標簽文本和計算尺寸。 -
交互邏輯的封裝
每個按鈕點擊時都會觸發一個交互,我們將交互邏輯封裝成一個可復用的函數。這個函數接受必要的信息,比如調試 ID 和要設置的類型,然后返回標準的交互數據結構,供按鈕點擊時使用。 -
按鈕排列與行布局
一個難點在于,現有的布局系統在處理每個元素后都會換行,而我們希望按鈕可以在一行中連續排列。為了解決這個問題,我們計劃引入一套新的布局機制,比如“按鈕行(Button Row)”,它不會在每個元素之后自動換行,而是手動管理位置偏移,使按鈕水平排列。 -
邏輯驅動設計
整個過程并沒有從“對象設計”出發,而是通過邏輯流程自然推導出所需的數據結構和模塊。我們認為:對象結構是良好程序設計的副產物,而不應是設計的出發點。
總結來說:
- 我們實現了一個可擴展的按鈕系統;
- 所有按鈕繪制和交互都可以在內部完成,使用簡單;
- 我們通過實際使用場景倒推系統結構,避免了冗余和設計偏差;
- 接下來將補充行內布局邏輯,使多個按鈕可以在一行中自然排列。
這套機制可以靈活地應用于性能分析器中的各類調試控件,也為將來加入更多 UI 控件打下了基礎。你希望繼續了解按鈕布局部分的具體實現方式嗎?
game_debug.cpp:引入BeginRow和EndRow來自動換行
我們在現有的 UI 布局系統中引入了一個新的概念:控制換行行為。原先的布局系統默認在每個元素繪制完成后會自動“回車換行”,也就是在垂直方向上堆疊 UI 元素。但這在我們希望橫向排列一排按鈕時帶來了問題,因此我們著手設計了一種新方式來避免不必要的換行。
具體來說,我們做了以下幾個關鍵調整和設計:
1. 引入行控制接口
我們添加了兩個新的布局接口:
begin_row()
end_row()
在調用 begin_row()
和 end_row()
包裹的代碼塊之間,布局系統會進入“禁用自動換行模式”,也就是元素繪制后不會自動跳轉到下一行。這就允許我們在橫向連續排列多個按鈕時,避免被強制換行。
為實現這一點,我們在布局系統中加入了一個標志(例如 no_line_feed
或一個嵌套計數器),用于標記當前是否在一個“禁止自動換行”的上下文中。
2. 取消行對象的返回值
在這種設計下,我們不再需要每一個“行對象”作為獨立的返回值存在。行本身不攜帶數據,僅僅是布局的控制語義,因此我們簡化了接口調用方式,使其更加清晰和輕量。
3. 復用已有的文本繪制邏輯
我們繼續使用已有的 basic_text_element
來繪制按鈕的文字內容,作為按鈕繪制的基礎。該函數提供了文本尺寸計算與基本繪制功能,是布局按鈕內容的核心。
為了配合按鈕的不同交互狀態(如是否高亮),我們也擴展了繪制邏輯,使按鈕在不同狀態下以不同方式渲染。例如啟用狀態使用一種顏色,禁用狀態使用另一種。
4. 統一參數傳遞
我們將 layout
對象作為參數傳遞給每一個 UI 元素的繪制函數,包括按鈕、文本等,這樣可以讓布局上下文在整個繪制過程中保持一致,便于控制尺寸、位置與排列。
5. 整體系統的遷移與整合
我們將舊的布局相關代碼從原始位置遷移到新的頭文件中(*.h
),整合所有相關邏輯。這一過程中我們清理了不再需要的返回值處理代碼,并補充了缺失的功能,確保 begin_row()
/ end_row()
能夠正確地控制布局行為。
6. 實際運行觀察效果
在初步實現完成后,我們運行了這套系統并觀察 UI 的呈現狀態。在測試中,我們能清晰看到按鈕被連續地繪制在同一行上,并且每個按鈕都有正確的標簽與交互區域。這驗證了我們的“無換行模式”布局邏輯是有效的。
7. 進一步的挑戰
盡管按鈕的排列和交互基本正常運行,但某些操作(例如“返回根節點”)的實現還面臨一些邏輯上的復雜性。這部分后續還需進一步優化交互邏輯與數據狀態處理。
總結
我們通過引入“禁止自動換行”的概念,為 UI 系統添加了一種更靈活的布局控制方式,使得按鈕等元素可以橫向排列。同時簡化了行對象的設計,復用了已有的文本繪制邏輯,并統一了布局參數傳遞,提升了整體系統的模塊化與可擴展性。
是否希望我繼續整理后續有關“按鈕交互邏輯”的實現細節?
game_debug.cpp:將Root設置為0
我們現在處理的是一個特殊交互場景:允許用戶通過某種方式返回根節點(root)。這里的核心邏輯是將“GUID”或“當前路徑”設置為 0
,來實現這一跳轉行為。我們探討并決定,這種跳轉應該被允許,而不是被限制或否決。
以下是我們當前實現中涉及的關鍵點與細節:
1. 設置GUID為 0 即代表返回根節點
我們采用了一種非常直接的方式來表示“返回根”的操作:將當前所在的GUID編號設為 0
。在系統邏輯中,GUID 0
被約定為根節點。因此任何時候只要將當前路徑指向該GUID,即可實現從任意子路徑或子菜單中退回主路徑。
2. 本來考慮是否應禁止這種行為
我們一度思考是否要對這種行為加以限制,比如不允許某些狀態下跳回根節點。但我們決定不做額外限制,默認允許用戶通過這種方式回到根節點,以簡化交互邏輯并提升使用體驗。
3. 需要同步更新交互提交邏輯
當前的實現方式雖然已經支持通過設為 0
返回根節點,但我們還需要同步修復或補充另外一個部分:與該交互相關的提交處理邏輯(submission logic)。
具體來說,需要在提交處理模塊(可能是 JIT 編譯邏輯、交互行為分發模塊、或狀態同步器)中識別出“設置為 0”的特殊含義,并正確處理:
- 將用戶狀態重置為根節點視圖;
- 清理當前上下文或棧幀;
- 更新 UI 狀態以反映已經回到主界面。
4. 后續修復點
我們意識到當前的 JIT 提交邏輯中可能還沒有很好地處理這種跳轉到根的情況。因此需要“修復 JIT 的附加提交部分(extra submission stuff)”,以保證跳回根節點時狀態同步完整,不會出現殘留視圖、無響應或狀態錯亂的問題。
總結
我們通過設置當前路徑或GUID編號為 0
來實現返回根節點的操作,雖然曾考慮是否加以限制,但最終決定開放這一功能。為了保證行為完整可靠,還需要對提交邏輯部分做進一步修復與補充,以完善整個交互閉環。
需要我繼續整理與“交互提交處理”相關的邏輯實現嗎?
運行游戲,發現無法與線程或幀進行交互
我們現在已經成功實現了可以隨時跳轉回根節點的功能,這部分行為已經完善,可以自由返回,不受限制。
當前問題
我們注意到界面上有兩個按鈕無法被點擊,點擊后沒有任何反應。起初我們不確定原因,但很快意識到這是因為——我們從未為這兩個按鈕實現對應的處理邏輯。
原因分析
- 這兩個按鈕雖然在界面中被渲染出來,但在點擊交互處理流程中沒有注冊對應的處理分支。
- 換句話說,它們在界面上“存在”,但在系統交互的判斷與分發邏輯中沒有對應的 case 分支或事件響應函數。
- 所以系統在點擊它們時,不知道應該做什么操作,因而導致無響應。
解決方向
- 立即補充交互邏輯:既然我們已經發現了這個遺漏,就應該趁著當前處理邏輯的上下文清晰的時候將這兩個按鈕納入交互處理分支中。
- 更新狀態分發:在主交互處理函數(如 switch-case 分支或映射表)中加入這兩個按鈕的處理路徑。
- 維護統一性:保證所有出現在界面中的元素,其對應的邏輯行為在系統內部也有定義,避免“視覺可見但行為失效”的不一致體驗。
總結
現在我們已經可以順暢返回根節點。但發現有兩個按鈕點擊無效的原因是它們缺少交互處理邏輯,并不是 bug。解決方案是立即在邏輯處理中添加相應分支或函數,確保每個可見按鈕都具備響應能力,避免用戶操作失敗或界面無反饋的問題。
是否還需要我繼續補充這兩個按鈕的交互邏輯應如何設計與添加?
點擊下一級 再點擊Root可以回到之前的Root
game_debug.cpp:實現SetElementType的情況
當前的開發重點是完善調試交互系統,使其更靈活、可擴展,尤其是在設置元素類型的過程中。我們正在嘗試從一個較為局限的實現,過渡到一個更通用的架構,使得調試交互能夠支持更多參數和更強的表達能力。
當前問題與背景
在原有的設計中,調試交互只能存儲一個參數,例如只能指定要設置的“類型”,但不能同時存儲與之相關的“元素”。這導致我們沒法準確指定要對哪個元素執行什么類型設置操作——這太局限了。
目標與思路
我們希望能實現這樣一種功能:
“把某個具體元素的類型設置為指定的值”,而且能靈活傳參。為此,我們需要:
- 支持將多個參數(如元素ID 和類型值)同時傳入交互對象;
- 讓交互可以具備通用性,而不是寫死行為;
- 實現一套“輕量級閉包”機制,使得這些參數可以在點擊或調用時生效。
實現細節
-
擴展交互結構:
我們不再局限于單參數,而是構造更豐富的交互內容,如:{ .element = x, .type = y }
這樣,每次創建交互時,可以自由指定“對哪個元素執行何種類型設置”。
-
修改設置行為的實現:
- 在
set_element_type_interaction
中,除了設置類型值,還顯式接收目標元素。 - 這樣一來,每個交互都變成了一個可以復用的“命令”,具備參數,具有明確目的。
- 在
-
處理邏輯更新:
- 檢查時不僅看類型是否匹配,還要對比元素ID是否相符;
- 這樣能精準判斷用戶當前交互是否命中目標。
-
行為觸發部分:
-
在真正執行設置時,我們只需調用:
element->type = desired_type;
所需的信息都已存在于參數中。
-
優點總結
- 靈活性大幅提升:任意元素可被設置為任意類型;
- 可重用性強:交互結構本質上變成了一個小型的命令對象;
- 結構清晰:每個交互都是明確行為 + 明確目標,避免歧義;
- 簡潔實現:邏輯雖更強大,但代碼結構依然簡明。
下一步
可以進一步在 UI 中測試這類交互是否響應準確,尤其是多個按鈕或元素存在時,是否可以正確設置目標元素的類型。
是否需要繼續補充調試交互系統在其他行為類型(如值賦予、狀態切換)中的擴展?
運行游戲,成功在線程和幀之間切換
現在我們已經能夠在這兩個元素之間切換,整體流程非常直接,操作上也比較清晰。
我們所做的調整讓交互行為具備了高度的通用性與靈活性,現在可以精確地控制對某個具體元素執行某種類型的設置。核心是通過傳入包含目標元素和目標類型的復合參數,使得交互行為不再受限于單一字段。
實現完成后,我們已經能夠在界面中方便地切換不同元素的狀態,這種切換邏輯在交互系統中表現得非常自然,響應也很及時。從使用角度看,這樣的機制非常適合構建調試工具或是內部編輯器,能夠即時看到元素類型的變更效果。
這也意味著,只要定義好所需的參數,后續無需專門為每個按鈕或操作編寫重復邏輯,而是通過同一套機制進行分發和處理。
目前實現非常穩定,操作也符合預期,后續可以考慮繼續拓展這個機制支持更多復雜的交互類型。是否需要加入一些可視化反饋來輔助切換狀態后的確認?
game_debug.cpp:引入AdvanceElement來處理間距和換行
現在我們希望優化按鈕排布的方式,避免每個按鈕都占據一整行,因此決定改進布局系統中“自動換行”的行為,使其可以靈活控制是否換行,支持更自然地將多個元素排成一排。
為此,我們引入了一個變量 no_line_feed
,它用于標記當前布局是否應該在添加新元素時進行換行。具體實現中,我們在處理元素布局時,根據 no_line_feed
的值來決定是移動 x
坐標(橫向排列)還是移動 y
坐標(縱向排列)。也就是說,如果開啟了 no_line_feed
,則會繼續在同一行向右排布元素;否則,在添加完元素后會進行換行。
為了實現這一點,我們將原來用于更新布局位置的邏輯抽象成一個更通用的函數 advance_element
,這個函數可以根據傳入的矩形參數決定如何更新布局位置。當結束一行(如調用 end_row
)時,我們會手動調用 advance_element
并傳入一個“空”矩形,代表當前這一排結束、需要換行回到左側。
這個“空”矩形的生成方式是使用當前布局位置 at
創建一個沒有實際尺寸的矩形(即 min == max
),這樣 advance_element
可以正確地識別這是“結束當前行”的信號。
在實現過程中我們發現還有其他幾點需要補充:
- 沒有定義
spacing_x
,這是元素橫向排列時的間距。為了解決這個問題,我們添加了一個spacing_x
變量,并在初始化布局時一起設置它,與spacing_y
一樣作為布局參數的一部分。 - 當前布局結構中沒有記錄每一行的初始
x
位置,這使得換行時無法正確回到最左側。因此我們新增了一個base_corner
或類似字段,用來記錄初始的橫坐標位置,確保每次換行都能正確歸位。 - 考慮到后續可能支持嵌套布局,我們設計布局系統時保留了向下兼容和擴展的能力,比如可以通過
begin_row
和end_row
成對控制換行行為。
這些調整讓整個布局系統更加靈活,既能自動換行也支持手動控制,便于后續實現更復雜的 UI 元素排列邏輯。整個流程實現清晰、邏輯自洽,也為未來擴展更多交互形式和布局行為打下了基礎。
是否還需要支持多層嵌套行或自動適應容器寬度?
運行游戲,看到結果更接近正確
首先,我們確認現有功能在調整后仍能正常運行,當前界面已經比之前更接近預期效果,因為多個元素已經可以出現在同一行中。但是目前還有一些問題沒有解決,主要集中在行內布局時元素的垂直對齊與換行位置不準確的問題。
核心問題出在換行邏輯處理上。以前布局系統假設每次只渲染一個元素后立即換行,因此在計算當前行的高度時,只需記錄該單個元素的 dim.y
(高度)即可,并以此來決定下一行的起始位置。但現在我們允許多個元素在同一行中橫向排列,這種處理方式就不再適用了,因為它只記錄了一個元素的高度,無法反映整行中所有元素中“最低”那個的底部位置。
我們發現當前的代碼中,在判斷是否需要換行、以及換行后定位下一行起始位置時,依然使用的是 get_max_corner(dim).y
(或類似邏輯),即只參考了當前元素自身的高度,而沒有綜合整行內所有元素的實際底部邊界。
為了解決這個問題,我們需要新增一個變量或邏輯,用于追蹤當前行中“最底部”元素的位置,確保在這一行結束時(調用 end_row
或自動換行)能準確地將新一行起始位置設置在整個上一行元素的下方。這意味著:
- 在一行中逐個添加元素時,每次都要檢查當前元素的底部(Y 軸最大值),與當前記錄的最低點進行比較并更新;
- 當這一行結束時,用這個最低 Y 值作為下一行的起點 Y 值,確保不會與上一行內容發生重疊或布局錯亂;
- 保證元素在縱向方向上有一致性排布,也為日后更復雜的排版規則(如垂直居中、行高控制等)打下基礎。
通過這種方式,我們就能更精準地控制布局系統中的換行邏輯,使其適用于更復雜的橫向元素排列場景,從而提升界面排版的穩定性與靈活性。
是否還需要對這一行內的元素進行垂直對齊處理?
game_debug.cpp:使AdvanceElement考慮行高
現在我們在一行中堆疊多個元素,因此需要追蹤整行中“最低”的元素位置,以確保正確的行間布局。在 advance_element
這一函數中,我們引入了新的邏輯來實現這一點。
具體來說,我們引入了一個變量,例如 NextYLine
或 NextYDelta
,用于記錄當前行中元素高度的最大值(也就是最低的 y 坐標)。每次加入一個元素后,我們會比較該元素的底部位置與之前記錄的最大值,更新 NextYLine
或 NextYDelta
,以確保我們始終知道當前行中最“低”的位置。
在下一次布局開始時,系統會使用這個變量來決定新行的起始位置,即:
- 將
layout.at_y
設置為當前的layout.at_y
加上NextYDelta
(即上一行中最大高度的變化量); - 然后清空
NextYDelta
為零,為下一行做準備; - 在沒有元素加入的情況下(即初始狀態),該變量默認是零,也就不會產生額外的換行偏移。
為了實現這個目標,我們在布局結構中新增了一個 NextYDelta
的字段(類型為 f32
),用于存儲這一行中需要額外下移的高度。每次添加元素時,系統通過比較當前位置與元素最大 y 邊界的差值來更新這個 delta。
這樣一來,整個自動換行和多元素排列的布局系統就能精確地控制行高,避免元素重疊或者行間距錯誤,提升了 UI 排版的準確性和靈活性。
接下來是否需要支持多層嵌套布局或更復雜的對齊規則?
運行游戲,看到我們正確的回車信息
目前已經實現了元素在同一行中合理排列的邏輯,并且能夠在多個元素之間保留預期的間距,同時支持按鈕狀態切換等功能。不過,當前的布局在水平方向上的間距仍不理想,表現為文本元素在行內被推進的距離比預期要多,導致整體布局顯得松散、不規整。
為了解決這個問題,重點需要檢查 advance_element
函數中使用的 total_bounds
參數。當前的 total_bounds
并不是真正的“尺寸”或“間距”維度(dim),而是該元素的矩形區域,這導致 advance_element
在進行布局推進時,可能使用了錯誤的數據進行位置更新,造成元素在行內的間隔過大。
為此,首先需要明確命名,避免混淆。將目前使用的 dim
更名為 element_rect
或其他能清晰表達其含義的變量名,有助于更直觀地理解其用途和限制。然后應檢查:
advance_element
函數的推進邏輯是否誤用了element_rect
的邊界作為推進距離;- 實際所需推進的距離應由元素的實際寬度加上預設的水平間距(spacing_x)決定;
- 如果當前推進邏輯是基于
rect.max.x - rect.min.x
,那么確認是否有未對齊的 padding、margin 或 baseline 導致尺寸膨脹; - 最好在計算推進距離時顯式使用元素的實際內容寬度而非整個外部邊界。
總之,當前關鍵是理清“元素大小”與“布局推進”之間的實際計算關系,避免使用錯誤的尺寸來源。接下來需要對照具體的布局調試輸出,進一步分析具體是哪一部分導致了不合理的橫向推進。
是否還需要調整垂直方向的基線對齊,或者進一步引入統一的 margin/padding 控制機制?
game_debug.cpp:將縮進放入AdvanceElement,而不是EndElement
目前我們發現布局中出現了不正常的縮進問題,懷疑根源在于深度(depth)或縮進(indent)值的處理方式有誤。我們原本在計算元素的最小位置(min corner)時就直接將縮進值加入,導致每一個元素的坐標都被多次疊加縮進,從而在水平布局中出現了異常的推進偏移。
為了解決這個問題,我們決定不再在計算元素邊界時加入縮進,而是在實際進行布局推進時(即 advance_element
階段)統一處理縮進。這樣,縮進的邏輯只作用于換行或行起始時的位置推進,而不會干擾元素本身的位置和尺寸計算。
我們在 advance_element
中找到正確的位置插入縮進計算邏輯:當我們回到基準位置(base corner)時,才將當前深度所帶來的偏移加上,確保縮進僅在這一環節生效。這樣布局推進更符合預期,也避免了錯誤的累計縮進。
不過,現在也發現了一個新的小問題:最初第一個元素似乎沒有正確處理縮進,推測是在初始階段深度值異常或未初始化所致,需要進一步排查深度值的來源和初始狀態。
修復后,布局中的元素現在能夠緊密排列,視覺上更加整潔。如果希望增加一些元素間的間距,可以選擇性地添加 spacing 參數,這樣就可以根據需求調整間距,而不是由錯誤邏輯被迫空出空間。
是否還需要我們進一步細化 spacing 或優化首個元素的布局初始化邏輯?
game_debug.cpp:使BasicTextElement更加響應式
我們當前的布局和交互系統已經有所改進,但仍有進一步優化空間。我們計劃對基礎文本元素(basic text element)的交互響應效果進行增強,使其在用戶交互時能表現得更加清晰、動態和直觀。
我們明確了如何判斷某個交互是否處于“激活”狀態,即所謂的 “hot” 狀態。通過使用一個 interaction_is_hot
的方法,我們可以檢測到當前元素是否處于鼠標懸停或被選中的狀態。
基于這個判斷邏輯,我們設置了一個新的布爾值 is_hot
,用來作為條件判斷的基礎。接著,我們希望文本元素在被交互時能顯示為“激活顏色”,否則就保持普通狀態的顏色。為了實現這一點,我們引入了兩種顏色變量:item_color
和 hot_color
。當元素處于激活狀態時,就渲染為 hot_color
;否則使用 item_color
。
雖然理論上這些顏色參數可以由調用者傳入,但我們覺得如果在組件內部就能處理好這類狀態轉換,會更加簡潔和易用。因此,我們傾向于將這兩個顏色值作為默認參數內建到組件中,使其具備自適應交互狀態的能力。這種默認處理方式使得組件在無需顯式指定顏色的情況下,也能根據交互狀態自動切換視覺反饋效果。
通過這種方式,我們不僅提升了交互的視覺表現,還讓基礎文本元素更加通用和響應性更強,有助于構建更流暢的用戶體驗界面。
是否需要進一步擴展此邏輯以支持不同風格或主題下的顏色配置?
運行游戲,看到鼠標懸停時的高亮顯示
現在我們已經實現了基本的高亮功能,在元素交互時會有明顯的視覺反饋,這是一個積極的改進。然而,目前仍存在一些問題需要處理,尤其是在處理那些實際上并沒有交互行為的元素時,它們也被錯誤地標記為“高亮”,這在視覺上是不準確的。
為了優化這個問題,我們意識到應該讓“高亮”狀態只針對實際存在交互的元素生效。因此我們引入了一種更嚴謹的判斷機制。在判斷某個元素是否處于“hot”狀態時,除了比較當前交互對象是否與目標交互一致之外,還應該確認該交互對象本身是真實存在的。換句話說,只有當某個交互類型是明確定義的并且與當前激活交互一致,才認為它是“hot”的。
我們考慮增加一個交互類型(interaction_type
)的有效性檢查。如果某個交互是“無效的”或“空的”(即不存在具體類型),那么這個交互對象就不能算是“hot”,從而避免誤判非交互元素為高亮狀態。這是為了防止視覺上不應有的高亮狀態出現,使界面更清晰、邏輯更合理。
在這一過程中我們還意識到存在一個復雜情況,那就是某些元素的交互類型是自動設置的(auto)。這會帶來一定的不確定性——我們在程序邏輯上無法立即判斷這些自動交互元素是否應當擁有“hot”狀態。這個問題有待進一步細化設計,也許需要重新考慮自動交互的默認行為或在渲染前就明確交互狀態。
盡管還有工作待完成,但目前的系統已經取得了一個穩固的階段性進展,具備了更合理的交互反饋機制,并為進一步完善鋪平了道路。是否要對自動交互類型的處理策略進行優化,是下一步值得深入探討的問題。
問答環節
(不是一個嚴肅的問題)為什么不直接導入npm并使用left pad來縮進字符串?
確實,這是一個非常有道理的建議。如果我們采用 mpm
并使用 left pad
來處理字符串縮進,整體的健壯性會顯著提升。
使用 left pad
的好處在于,我們可以統一且清晰地控制每一行的縮進邏輯,而不必手動管理空格數量或其他對齊細節。這在處理多層嵌套結構或者排版敏感的 UI 輸出時尤為重要。手動實現縮進往往容易出錯,比如漏掉某一層的偏移,或因格式變化導致縮進混亂。而使用 left pad
這樣的通用方法庫,可以將這一邏輯抽象成一行代碼,既減少維護成本,又提升可讀性和一致性。
此外,引入專門的文本處理工具還能提升代碼的可組合性和可擴展性。如果后續我們需要根據某種主題樣式調整縮進層級,僅需修改一個參數即可完成整體風格的更改,而不必全局手動替換。也便于做一些動態樣式調整,比如根據某種邏輯條件對不同模塊增加或減少縮進。
所以如果一開始就選擇這樣的方式,會讓整個系統在格式化文本方面更加靈活、穩定,也能避免后來為手工處理縮進埋下的各種隱患。這是一種更加系統化、工程化的做法,對長期維護非常有益。我們會認真考慮是否將這種方式引入現有架構,以提升整體的設計質量。你覺得是否應該立即開始重構現有的縮進邏輯?
我覺得你在選擇地面作為實體?
我們在實現過程中提到選擇地面作為實體(entity)的一種情況,并探討了這樣做是否存在問題。總結如下:
我們認為將地面作為一個可選實體可能存在一定的隱患或副作用,具體包括:
-
邏輯混亂風險
地面通常是一個靜態背景或基礎部分,不具備與普通游戲對象相同的交互邏輯。如果也作為“可選擇實體”處理,可能導致選擇系統在處理點擊或焦點判斷時邏輯變得復雜,無法清晰地區分玩家是想選擇可交互物體,還是意外地“選擇了地面”。 -
影響其他交互
如果地面也進入了可交互或可選擇的實體列表中,可能會攔截掉原本應該傳遞給其他物體的事件(如點擊、拖拽等),導致交互異常或行為不一致。 -
渲染或狀態反饋問題
選中實體后通常會附帶一些反饋效果,例如高亮、顯示輪廓、浮動 UI 等,而地面被選中后不太容易提供這些視覺反饋,這會造成 UX(用戶體驗)上的困惑或不一致。 -
后續擴展性問題
如果地面是可選的,在設計更復雜的系統(如單位尋路、右鍵交互、編輯器操作等)時,需要額外判斷是否選中的是“特殊實體”,會使代碼維護成本提高。
因此,從結構和設計的清晰性角度考慮,通常我們會避免將地面納入可選擇實體體系中,而是專門處理為一種特殊類型(例如背景層、靜態碰撞層等),并在交互系統中明確區別對待。
你這邊是希望用于游戲編輯器選擇系統、交互邏輯,還是別的場景?
@pseudonym73 $$見資源,John W. Peterson]
我們在閱讀一篇 PDF 文獻時,快速瀏覽了其內容,得出以下詳細總結:
這篇文獻主要研究如何將一條曲線重新參數化,使得它在參數空間中的采樣點間距與實際的弧長一致,也就是說,實現一種等弧長(arc length)參數化的方式。原始的曲線參數化通常并不具備這樣的特性,導致在渲染或計算時,點的分布密度不均勻,影響視覺或計算效果。
該方法采取了一種**迭代細分(iterative subdivision)**的策略:
-
目標是將曲線重參數化為弧長均勻分布的形式,也就是希望在視覺上或數學上,點與點之間的距離更為均勻。
-
輸入曲線可能是通過傳統方式構造的,例如 Bézier 曲線、樣條曲線等,這些方式的參數化通常不是基于弧長的。
-
核心思想是:
- 先對曲線進行初步采樣,得到一系列點。
- 通過計算這些點之間的實際弧長,判斷分布是否均勻。
- 如果不均勻,就不斷細分曲線,將參數值調整,使得重新采樣后點之間的實際距離更趨近一致。
- 這個過程是迭代的,直到采樣點間的距離滿足設定的誤差閾值。
-
實際應用場景包括路徑規劃、物體沿曲線運動、精確紋理映射、視覺上均勻的動畫分布等。
-
優點在于結果能提供更加平滑、均勻的控制點分布,尤其適用于對“視覺一致性”要求較高的領域,比如 UI 動畫或圖形渲染。
我們暫時還沒有深入閱讀全文內容,但從快速掃讀來看,這種方法是完全合理且實用的,并且很適合用于從不均勻曲線參數化中構建更自然、規律化的版本。
你想進一步了解這個算法的具體實現嗎?我可以幫你解釋它的細節或偽代碼。
你知道如果你給HAL加一個字母就能得到IBM嗎?你知道如果你給VMS加一個字母會得到WNT嗎?
我們提到了一個有趣的說法,也算是一個“都市傳說”:
如果在 “IBM” 這個縮寫中加一個字母,可以得到 “IBMS”;
而 “MS”(Microsoft 的縮寫)再加一個字母“T”,就變成了 “MST”,也就是 “Windows NT” 中的 “NT” 開頭的形式。
這只是一個巧合或玩笑性質的說法,并沒有確鑿證據表明這是產品命名的真實來源。我們聽說過這種說法,但是否真的是微軟命名“Windows NT”的原因,并不確定。
所以這個接口系統是一個非常簡單的即時模式設計嗎?
這個界面系統采用的是一種非常簡單的即時模式(Immediate Mode)設計。
嚴格來說,并不算特別“純粹”的即時模式,因為我們為了避免交互時出現一幀延遲,使用了交互信息的存儲機制。具體來說,交互行為(如點擊、拖動等)是立即響應的,不存在一幀的延遲,這對于用戶操作的流暢性非常重要。
但在高亮(highlight)反饋方面,仍然存在一幀的延遲。也就是說,當鼠標移動到某個元素上時,該元素的高亮效果可能要等到下一幀才會顯示出來。不過這種延遲對整體使用體驗影響不大。
理論上,如果不使用交互存儲,而是將所有交互邏輯都寫在界面繪制流程中(例如在每次繪制時直接判斷交互),那么可以實現一個更加簡單的即時模式系統。但那種方式在響應速度和靈活性上可能會有一些不足。
所以當前設計在保持簡單的同時,又通過適度的狀態存儲,兼顧了性能與用戶體驗。
為什么有時候在分析窗口外面會出現一些藍色的線條?
我們在性能分析視圖中會看到一些藍色的線條,它們的作用是用來表示某些線程活動的持續時間。
通常情況下,我們會將每個線程的活動分別顯示出來,也就是說,每一條線程都有自己獨立的顯示軌跡。如果某個線程上存在一個耗時較長的操作,比如資源加載,這個操作可能會跨越多個幀。也就是說,它可能在一個幀的開始被觸發,然后一直持續到下一個幀甚至更久。
由于這些加載操作是在一個獨立的線程上執行的,而這個線程本身并不關心幀的邊界,它只是啟動了任務然后持續執行,因此這些操作就可能“溢出”出當前的幀窗口范圍。而藍色線條的作用就是清晰地表示出這個跨幀執行的行為。
我們在調試界面中并不會刻意去隱藏或調整這些跨幀的操作,因為它們確實反映了實際發生的情況。藍線從一個幀開始,一直延伸到另一個幀的現象,恰恰說明該線程的執行時間長于一個渲染幀周期。
目前我們也沒有特別明確的視覺設計方案來“美化”這種跨幀行為的呈現方式,因為這是一個用于調試的工具,目標是準確呈現線程狀態,而不是追求視覺上的精致。
總的來說,這些藍線用于標示那些在某一幀開始但持續到下一幀的長時間線程任務,主要用于幫助識別異步加載等長耗時操作在多線程環境中的執行情況。
VS2015,可以使用clang作為編譯器
我們討論到在 Visual Studio 2020 中使用 clang 作為編譯器的可行性。
表面上看似乎可以,但實際上存在一些關鍵問題,使得它并不真正可行。主要的問題在于 clang 并不支持 Windows 平臺上使用的復雜聲明機制,尤其是像 __declspec
這樣的聲明規范,而這是在 Windows 編程中非常常見和重要的功能。
這些聲明通常涉及到導出符號、調用約定、類型信息等內容,而 clang 目前并不具備處理這類特性所需的能力。由于缺乏這方面的支持,它無法處理那些通過復雜聲明方式定義的類型和接口,因此也無法正確編譯包含這些聲明的 Windows API 或系統相關代碼。
如果要在 Windows 上實際使用 clang 編譯 Windows 程序,勢必要處理這些聲明,或者模擬其行為,否則就無法鏈接或運行。因此,從目前的角度來看,clang 不能勝任作為一個完整的 Windows 應用程序編譯器,除非它添加對這些關鍵語言特性的支持。
總結來說,clang 理論上可以作為編譯器的一部分使用,但由于不支持 Windows 特有的復雜聲明語法,實際上并不能完整勝任在 Windows 環境下構建標準應用程序的任務。
你在使用clang-cl嗎?
我們正在使用 Clang 編譯器中的 clang-cl
,這是一個與 Microsoft Visual C++ 編譯器兼容的 Clang 前端,能夠支持 Windows 平臺上的編譯需求。
在討論中,提到的其他問題似乎已經得到了解答,因此暫時沒有新的問題需要討論。
你之前提到過對象作為系統需求的自然結果出現。但你并不是指C++對象嗎?那你指的對象是什么?
我們提到的對象是指在代碼中自然產生的實體,這些對象并不是強制性地預設或者刻意設計出來的,而是在合理的系統架構和工程實現下,自然生成的。在系統的運作過程中,隨著需求的變化和代碼結構的演變,某些概念或者數據結構會逐步演化成“對象”,這些對象可能并沒有嚴格的定義,更多是根據上下文和需求來理解和使用。
舉個例子,比如某些代碼段可能會自然生成一些結構體或類,它們在處理特定任務時作為“對象”來使用,而這些對象并不是從一開始就定義好的,而是在編碼的過程中根據需要而逐漸體現出來。這種情況的“對象”并不是一開始預設的,而是代碼工程的自然結果。
game_render_group.h:演示“對象”如何自然而然地從代碼中產生
在開發過程中,渲染組(render group)是一個簡單的系統,用于處理渲染和緩沖。我們并沒有特別去考慮結構如何設計,或是如何將不同的功能分配到不同的結構中,因為這些問題在編程時并不需要思考。換句話說,開發時不需要預設哪些數據應該捆綁在一起,也不需要刻意考慮對象的設計。在實踐中,真正重要的是我們要做什么,而不是數據如何組織。通過編寫代碼和實現功能,我們逐漸發現哪些數據應該捆綁在一起,哪些功能需要哪些數據。
例如,在編寫渲染組相關的代碼時,最終我們會發現“對象轉換”和“渲染組”應該是兩個不同的概念。這是在編程過程中逐步理解出來的,而非一開始就預設的。這正是我所說的對象是在工程中自然而然出現的,而不是刻意創建的。
在面向對象編程(OOP)中,我認為關注點不應該是對象本身,因為這會導致低效且容易出錯的設計。正確的做法是專注于代碼需要完成的任務,理解這些任務如何影響數據的結構,并通過合理的方式組織數據,以便功能能夠有效地執行。
如果在編碼過程中開始過度關注“對象”以及如何給對象加上屬性和行為,反而會使系統變得更加復雜且難以維護。在我看來,面向對象編程不是一種好的方法,正確的方式是通過編寫代碼來自然地發現數據如何組織和結構化,而不需要事先設定對象的形式。
至于系統的可復用性和可擴展性,在開發過程中,如果想讓代碼更具可重用性、庫化并能夠共享給其他團隊成員,所做的改變僅僅是將代碼從頭文件(.h)中移到源文件(.cpp)中,并只在頭文件中暴露出外部需要訪問的接口和功能。這樣,其他團隊成員只會看到他們需要的內容,而不會被不必要的細節干擾。
通過這種方式,你可以將代碼分成合理的模塊,只暴露需要的功能,確保架構易于使用且持久。編寫易于維護和長時間使用的架構沒有其他更復雜的步驟,理解這些原則是實現高質量架構的關鍵。
@cubercaleb 你在object_transform的注釋里有一個拼寫錯誤
在這個過程中,討論了關于某個命名的拼寫錯誤(typo)。這種錯誤可能會導致很多不必要的錯誤和問題,然而實際上,這種拼寫并不算錯誤,反而是故意為之的。有時候,這種拼寫看起來像是口音或者習慣的體現,或者是某種特定的表達方式。其他時候,這種用法會被故意分離出來,形成自己的風格或俚語。
這種做法是有意識的,并不是偶然的錯誤,因此它是被刻意設計成這種方式的。在一些情況下,可能會選擇將這些用法分開,形成自己的特色或者是專有的用語。