From c26ddeef4dd231e7fb6a58d2c421623b4c1c3b57 Mon Sep 17 00:00:00 2001 From: Cyberarm Date: Thu, 20 Apr 2023 16:08:59 -0500 Subject: [PATCH] Refactored CyberarmEngine::Stats to track data for last N frames --- .../opengl/renderer/renderer.rb | 13 ++- lib/cyberarm_engine/stats.rb | 91 +++++++++++++++++-- lib/cyberarm_engine/ui/elements/container.rb | 4 +- lib/cyberarm_engine/ui/gui_state.rb | 7 ++ lib/cyberarm_engine/window.rb | 25 ++++- 5 files changed, 123 insertions(+), 17 deletions(-) diff --git a/lib/cyberarm_engine/opengl/renderer/renderer.rb b/lib/cyberarm_engine/opengl/renderer/renderer.rb index 3e58575..1575d51 100644 --- a/lib/cyberarm_engine/opengl/renderer/renderer.rb +++ b/lib/cyberarm_engine/opengl/renderer/renderer.rb @@ -8,8 +8,19 @@ module CyberarmEngine end def draw(camera, lights, entities) + Stats.frame.start_timing(:opengl_renderer) + + Stats.frame.start_timing(:opengl_model_renderer) @opengl_renderer.render(camera, lights, entities) - @bounding_box_renderer.render(entities) if @show_bounding_boxes + Stats.frame.end_timing(:opengl_model_renderer) + + if @show_bounding_boxes + Stats.frame.start_timing(:opengl_boundingbox_renderer) + @bounding_box_renderer.render(entities) + Stats.frame.end_timing(:opengl_boundingbox_renderer) + end + + Stats.frame.end_timing(:opengl_renderer) end def canvas_size_changed diff --git a/lib/cyberarm_engine/stats.rb b/lib/cyberarm_engine/stats.rb index 459c296..3dbb8d7 100644 --- a/lib/cyberarm_engine/stats.rb +++ b/lib/cyberarm_engine/stats.rb @@ -1,20 +1,91 @@ module CyberarmEngine class Stats - @@hash = { - gui_recalculations_last_frame: 0 - } + @frames = [] + @frame_index = -1 + @max_frame_history = 1024 - def self.get(key) - @@hash.dig(key) + def self.new_frame + if @frames.size < @max_frame_history + @frames << Frame.new + else + @frames[@frame_index] = Frame.new + end end - def self.increment(key, n) - @@hash[key] += n + def self.frame + @frames[@frame_index] end - def self.clear - @@hash.each do |key, _value| - @@hash[key] = 0 + def self.end_frame + frame&.complete + + @frame_index += 1 + @frame_index %= @max_frame_history + end + + def self.frames + if @frames.size < @max_frame_history + @frames + else + @frames.rotate(@frame_index - (@max_frame_history - (@frames.size - 1))) + end + end + + def self.frame_index + @frame_index + end + + def self.max_frame_history + @max_frame_history + end + + class Frame + Timing = Struct.new(:start_time, :end_time, :duration) + + attr_reader :frame_timing, :counters, :timings + def initialize + @frame_timing = Timing.new(start_time: Gosu.milliseconds, end_time: -1, duration: -1) + + @counters = { + gui_recalculations: 0 + } + + @timings = {} + end + + def increment(key, number = 1) + @counters[key] ||= 0 + @counters[key] += number + end + + def start_timing(key) + raise "key must be a symbol!" unless key.is_a?(Symbol) + warn "Only one timing per key per frame. (Timing for #{key.inspect} already exists!)" if @timings[key] + + @timings[key] = Timing.new(start_time: Gosu.milliseconds, end_time: -1, duration: -1) + end + + def end_timing(key) + timing = @timings[key] + + warn "Timing #{key.inspect} already ended!" if timing.end_time != -1 + + timing.end_time = Gosu.milliseconds + timing.duration = timing.end_time - timing.start_time + end + + def complete + @frame_timing.end_time = Gosu.milliseconds + @frame_timing.duration = @frame_timing.end_time - @frame_timing.start_time + + # Lock data structures + @frame_timing.freeze + @counters.freeze + @timings.freeze + end + + def complete? + @frame_timing.duration != -1 end end end diff --git a/lib/cyberarm_engine/ui/elements/container.rb b/lib/cyberarm_engine/ui/elements/container.rb index c06bdfa..11603f5 100644 --- a/lib/cyberarm_engine/ui/elements/container.rb +++ b/lib/cyberarm_engine/ui/elements/container.rb @@ -118,7 +118,7 @@ module CyberarmEngine return unless visible? - Stats.increment(:gui_recalculations_last_frame, 1) + Stats.frame.increment(:gui_recalculations) stylize @@ -176,7 +176,7 @@ module CyberarmEngine child.recalculate child.reposition # TODO: Implement top,bottom,left,center, and right positioning - Stats.increment(:gui_recalculations_last_frame, 1) + Stats.frame.increment(:gui_recalculations) child.element_visible = child.x >= @x - child.width && child.x <= @x + width && child.y >= @y - child.height && child.y <= @y + height diff --git a/lib/cyberarm_engine/ui/gui_state.rb b/lib/cyberarm_engine/ui/gui_state.rb index d4455f8..6802f43 100644 --- a/lib/cyberarm_engine/ui/gui_state.rb +++ b/lib/cyberarm_engine/ui/gui_state.rb @@ -79,16 +79,23 @@ module CyberarmEngine def update if @pending_recalculate_request + Stats.frame.start_timing(:gui_recalculate) @root_container.recalculate @root_container.recalculate @root_container.recalculate @pending_recalculate_request = false + + Stats.frame.end_timing(:gui_recalculate) end + Stats.frame.start_timing(:gui_element_recalculate_requests) + @pending_element_recalculate_requests.each(&:recalculate) @pending_element_recalculate_requests.clear + Stats.frame.end_timing(:gui_element_recalculate_requests) + if @pending_focus_request @pending_focus_request = false diff --git a/lib/cyberarm_engine/window.rb b/lib/cyberarm_engine/window.rb index f76833e..ab3cadd 100644 --- a/lib/cyberarm_engine/window.rb +++ b/lib/cyberarm_engine/window.rb @@ -8,14 +8,14 @@ module CyberarmEngine attr_accessor :show_cursor attr_writer :exit_on_opengl_error - attr_reader :last_frame_time, :states + attr_reader :last_frame_time, :delta_time, :states def self.now Gosu.milliseconds end def self.dt - instance.last_frame_time / 1000.0 + instance.dt end def self.instance=(window) @@ -37,6 +37,7 @@ module CyberarmEngine @last_frame_time = Gosu.milliseconds - 1 @current_frame_time = Gosu.milliseconds + @delta_time = @last_frame_time self.caption = "CyberarmEngine #{CyberarmEngine::VERSION} #{Gosu.user_languages.join(', ')}" @states = [] @@ -50,16 +51,32 @@ module CyberarmEngine end def draw + Stats.frame.start_timing(:draw) + current_state&.draw + + Stats.frame.end_timing(:draw) + Stats.frame.start_timing(:interframe_sleep) end def update - Stats.clear + # Gosu calls update() then (optionally) draw(), + # so always end last frame and start next frame when update() is called. + Stats.frame&.end_timing(:interframe_sleep) + Stats.end_frame - current_state&.update + Stats.new_frame + + @delta_time = (Gosu.milliseconds - @current_frame_time) * 0.001 @last_frame_time = Gosu.milliseconds - @current_frame_time @current_frame_time = Gosu.milliseconds + + Stats.frame.start_timing(:update) + current_state&.update + Stats.frame.end_timing(:update) + + Stats.frame.start_timing(:interframe_sleep) unless needs_redraw? end def needs_cursor?