cruise speed and ui test

This commit is contained in:
firestar5683
2026-04-06 22:38:20 -05:00
parent ad948aa93e
commit 3ca76720fc
40 changed files with 445 additions and 30 deletions
+10 -4
View File
@@ -2,21 +2,27 @@ import os
import time
import struct
from openpilot.system.hardware.hw import Paths
from openpilot.common.swaglog import cloudlog
WATCHDOG_FN = f"{Paths.shm_path()}/wd_"
_LAST_KICK = 0.0
_LAST_ERROR_LOG = 0.0
def kick_watchdog():
global _LAST_KICK
global _LAST_KICK, _LAST_ERROR_LOG
current_time = time.monotonic()
if current_time - _LAST_KICK < 1.0:
return
return True
try:
with open(f"{WATCHDOG_FN}{os.getpid()}", 'wb') as f:
f.write(struct.pack('<Q', int(current_time * 1e9)))
f.flush()
_LAST_KICK = current_time
except OSError:
pass
return True
except OSError as e:
if current_time - _LAST_ERROR_LOG >= 5.0:
cloudlog.error(f"watchdog kick failed for pid {os.getpid()}: {e}")
_LAST_ERROR_LOG = current_time
return False
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1,2 +1,2 @@
extern const uint8_t gitversion[19];
const uint8_t gitversion[19] = "DEV-f1feaa2c-DEBUG";
const uint8_t gitversion[19] = "DEV-ad948aa9-DEBUG";
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
+1 -1
View File
@@ -1 +1 @@
DEV-f1feaa2c-DEBUG
DEV-ad948aa9-DEBUG
+3 -9
View File
@@ -50,14 +50,6 @@ class VCruiseHelper:
def _get_cruise_delta_interval(interval: float | None) -> float:
return interval if isinstance(interval, (int, float)) and interval > 0 else 1.0
def _normalize_initialized_v_cruise(self, v_cruise_kph: float, starpilot_toggles: SimpleNamespace) -> float:
cruise_increase = self._get_cruise_delta_interval(starpilot_toggles.cruise_increase)
if cruise_increase % 5 != 0:
return v_cruise_kph
v_cruise_delta = self._get_short_press_delta(starpilot_toggles.is_metric, starpilot_toggles)
return round(round(v_cruise_kph / v_cruise_delta) * v_cruise_delta, 1)
@property
def v_cruise_initialized(self):
return self.v_cruise_kph != V_CRUISE_UNSET
@@ -162,7 +154,9 @@ class VCruiseHelper:
and self.v_cruise_initialized or (self.gm_cc_only and resume_prev_button)):
self.v_cruise_kph = self.v_cruise_kph_last
elif desired_speed_limit > 0 and getattr(starpilot_toggles, "set_speed_limit", False):
initialized_speed_limit_kph = self._normalize_initialized_v_cruise(desired_speed_limit * CV.MS_TO_KPH, starpilot_toggles)
# Respect the exact SLC limit+offset on engage instead of snapping upward to
# the custom cruise-button interval.
initialized_speed_limit_kph = round(desired_speed_limit * CV.MS_TO_KPH, 1)
self.v_cruise_kph = float(np.clip(initialized_speed_limit_kph, V_CRUISE_MIN, V_CRUISE_MAX))
else:
self.v_cruise_kph = int(round(np.clip(CS.vEgo * CV.MS_TO_KPH, engage_floor_kph, V_CRUISE_MAX)))
+44
View File
@@ -207,6 +207,22 @@ class TestVCruiseHelper:
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(55 * CV.MPH_TO_KPH)
def test_initialize_v_cruise_keeps_exact_speed_limit_offset(self):
self.reset_cruise_speed_state()
self.starpilot_toggles.set_speed_limit = True
self.starpilot_toggles.cruise_increase = 5
desired_speed_limit = 38 * CV.MPH_TO_MS
self.v_cruise_helper.initialize_v_cruise(
car.CarState(vEgo=70 * CV.MPH_TO_MS),
experimental_mode=False,
resume_prev_button=False,
starpilot_toggles=self.starpilot_toggles,
desired_speed_limit=desired_speed_limit,
)
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(38 * CV.MPH_TO_KPH)
def test_speed_limit_confirmation_does_not_adjust_cruise(self):
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph
@@ -260,3 +276,31 @@ class TestVCruiseHelper:
)
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(initial_v_cruise_kph + IMPERIAL_INCREMENT)
def test_zero_custom_cruise_toggles_fall_back_to_single_step(self):
self.enable(V_CRUISE_INITIAL * CV.KPH_TO_MS, False)
initial_v_cruise_kph = self.v_cruise_helper.v_cruise_kph
self.starpilot_toggles.cruise_increase = 0
self.starpilot_toggles.cruise_increase_long = 0
pressed_cs = car.CarState(cruiseState={"available": True})
pressed_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=True)]
self.v_cruise_helper.update_v_cruise(
pressed_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
released_cs = car.CarState(cruiseState={"available": True})
released_cs.buttonEvents = [ButtonEvent(type=ButtonType.accelCruise, pressed=False)]
self.v_cruise_helper.update_v_cruise(
released_cs,
enabled=True,
is_metric=False,
speed_limit_changed=False,
starpilot_toggles=self.starpilot_toggles,
)
assert self.v_cruise_helper.v_cruise_kph == pytest.approx(initial_v_cruise_kph + IMPERIAL_INCREMENT)
@@ -576,7 +576,7 @@ class StarPilotLongitudinalQOLLayout(StarPilotPanel):
{
"title": tr_noop("Cruise Interval"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CustomCruise')} mph",
"get_value": lambda: f"{max(1, self._params.get_int('CustomCruise'))} mph",
"on_click": lambda: self._show_speed_selector("CustomCruise"),
"color": "#597497",
"visible": lambda: self._params.get_bool("QOLLongitudinal"),
@@ -584,7 +584,7 @@ class StarPilotLongitudinalQOLLayout(StarPilotPanel):
{
"title": tr_noop("Cruise Long"),
"type": "value",
"get_value": lambda: f"{self._params.get_int('CustomCruiseLong')} mph",
"get_value": lambda: f"{max(1, self._params.get_int('CustomCruiseLong'))} mph",
"on_click": lambda: self._show_speed_selector("CustomCruiseLong"),
"color": "#597497",
"visible": lambda: self._params.get_bool("QOLLongitudinal"),
@@ -662,7 +662,8 @@ class StarPilotLongitudinalQOLLayout(StarPilotPanel):
self._params.put_int(key, int(val))
self._rebuild_grid()
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 0, 100, 1, self._params.get_int(key), on_close, unit=" mph", color="#597497"))
current = max(1, self._params.get_int(key))
gui_app.set_modal_overlay(AetherSliderDialog(tr(key), 1, 100, 1, current, on_close, unit=" mph", color="#597497"))
def _show_int_selector(self, key, min_v, max_v, unit=""):
def on_close(res, val):
+131
View File
@@ -0,0 +1,131 @@
import os
import sys
import time
import traceback
import threading
from pathlib import Path
from openpilot.common.swaglog import cloudlog
def _default_dump_dir() -> Path:
for candidate in ("/data/log", "/tmp"):
if os.path.isdir(candidate) and os.access(candidate, os.W_OK):
return Path(candidate)
return Path.cwd()
class UIStallMonitor:
def __init__(self, name: str):
self._name = name
self._threshold_s = float(os.getenv("UI_STALL_PROBE_MAX_DT", "5"))
self._poll_s = float(os.getenv("UI_STALL_PROBE_POLL_DT", "0.25"))
self._dump_dir = _default_dump_dir()
self._main_thread_id = threading.get_ident()
now = time.monotonic()
self._last_progress = now
self._phase = "init"
self._phase_entered = now
self._stall_reported = False
self._stalled_since = now
self._stalled_phase = self._phase
self._lock = threading.Lock()
self._stop_event = threading.Event()
self._thread = threading.Thread(target=self._run, name=f"{name}_stall_probe", daemon=True)
def start(self) -> None:
if self._threshold_s <= 0.0:
return
self._thread.start()
def stop(self) -> None:
if self._threshold_s <= 0.0:
return
self._stop_event.set()
self._thread.join(timeout=1.0)
def progress(self, phase: str) -> None:
now = time.monotonic()
recovered = None
with self._lock:
if phase != self._phase:
self._phase = phase
self._phase_entered = now
self._last_progress = now
if self._stall_reported:
recovered = (now - self._stalled_since, self._stalled_phase, phase)
self._stall_reported = False
if recovered is not None:
stalled_for_s, stalled_phase, current_phase = recovered
cloudlog.warning(f"{self._name} stall recovered after {stalled_for_s:.1f}s (stalled_phase={stalled_phase}, current_phase={current_phase})")
def _run(self) -> None:
while not self._stop_event.wait(self._poll_s):
now = time.monotonic()
with self._lock:
stalled_for_s = now - self._last_progress
phase = self._phase
phase_for_s = now - self._phase_entered
already_reported = self._stall_reported
if stalled_for_s < self._threshold_s or already_reported:
continue
dump = self._build_dump(now, phase, stalled_for_s, phase_for_s)
dump_path = self._write_dump(dump)
with self._lock:
self._stall_reported = True
self._stalled_since = now
self._stalled_phase = phase
preview = self._main_thread_preview()
path_s = str(dump_path) if dump_path is not None else "<write_failed>"
cloudlog.error(f"{self._name} main loop stalled for {stalled_for_s:.1f}s in phase={phase} (phase_for={phase_for_s:.1f}s) dump={path_s}\n{preview}")
def _build_dump(self, now: float, phase: str, stalled_for_s: float, phase_for_s: float) -> str:
frames = sys._current_frames()
threads = {thread.ident: thread for thread in threading.enumerate()}
lines = [
f"name={self._name}",
f"pid={os.getpid()}",
f"wall_time={time.strftime('%Y-%m-%dT%H:%M:%S%z')}",
f"monotonic={now:.6f}",
f"stalled_for_s={stalled_for_s:.3f}",
f"phase={phase}",
f"phase_for_s={phase_for_s:.3f}",
"",
]
ordered_idents = sorted(frames.keys(), key=lambda ident: ident != self._main_thread_id)
for ident in ordered_idents:
thread = threads.get(ident)
name = thread.name if thread is not None else "<unknown>"
daemon = thread.daemon if thread is not None else None
lines.append(f"Thread {name} ident={ident} daemon={daemon}")
lines.extend(traceback.format_stack(frames[ident]))
lines.append("")
return "".join(line if line.endswith("\n") else f"{line}\n" for line in lines)
def _main_thread_preview(self) -> str:
frame = sys._current_frames().get(self._main_thread_id)
if frame is None:
return "main_thread_stack=<unavailable>"
stack_lines = traceback.format_stack(frame)
preview = "".join(stack_lines[-8:]).strip()
return f"main_thread_stack:\n{preview}" if preview else "main_thread_stack=<empty>"
def _write_dump(self, dump: str) -> Path | None:
timestamp = time.strftime("%Y%m%d_%H%M%S")
path = self._dump_dir / f"{self._name}_stall_{os.getpid()}_{timestamp}.log"
try:
path.write_text(dump)
return path
except OSError as e:
cloudlog.error(f"failed to write {self._name} stall dump to {path}: {e}")
return None
+199 -1
View File
@@ -1,7 +1,21 @@
#include "selfdrive/ui/ui.h"
#include <algorithm>
#include <atomic>
#include <cerrno>
#include <cmath>
#include <chrono>
#include <csignal>
#include <cstdio>
#include <cstdlib>
#include <execinfo.h>
#include <fcntl.h>
#include <mutex>
#include <pthread.h>
#include <sys/syscall.h>
#include <sys/types.h>
#include <thread>
#include <unistd.h>
#include <QtConcurrent>
@@ -14,6 +28,178 @@
#define BACKLIGHT_DT 0.05
#define BACKLIGHT_TS 10.00
namespace {
enum class UIStallPhase {
INIT = 0,
UPDATE_START,
AFTER_SOCKETS,
AFTER_STATE,
AFTER_STATUS,
AFTER_WATCHDOG,
AFTER_EMIT,
AFTER_FS_UPDATE,
IDLE,
};
std::atomic<uint64_t> ui_stall_last_progress_ns{0};
std::atomic<int> ui_stall_phase{static_cast<int>(UIStallPhase::INIT)};
std::atomic<uint64_t> ui_stall_frame{0};
std::atomic<bool> ui_stall_reported{false};
std::atomic<uint64_t> ui_stall_reported_ns{0};
std::atomic<int> ui_stall_reported_phase{static_cast<int>(UIStallPhase::INIT)};
std::atomic<int> ui_stall_dump_fd{-1};
pthread_t ui_main_thread{};
double read_env_double(const char *name, double default_value) {
const char *value = std::getenv(name);
if (value == nullptr || *value == '\0') {
return default_value;
}
char *end = nullptr;
double parsed = std::strtod(value, &end);
if (end == value || (end != nullptr && *end != '\0') || parsed <= 0.0) {
return default_value;
}
return parsed;
}
const char *ui_stall_phase_name(UIStallPhase phase) {
switch (phase) {
case UIStallPhase::INIT: return "init";
case UIStallPhase::UPDATE_START: return "update_start";
case UIStallPhase::AFTER_SOCKETS: return "after_sockets";
case UIStallPhase::AFTER_STATE: return "after_state";
case UIStallPhase::AFTER_STATUS: return "after_status";
case UIStallPhase::AFTER_WATCHDOG: return "after_watchdog";
case UIStallPhase::AFTER_EMIT: return "after_emit";
case UIStallPhase::AFTER_FS_UPDATE: return "after_fs_update";
case UIStallPhase::IDLE: return "idle";
}
return "unknown";
}
std::string ui_stall_dump_dir() {
return access("/data/log", W_OK) == 0 ? "/data/log" : "/tmp";
}
void ui_stall_signal_handler(int sig) {
const int fd = ui_stall_dump_fd.load(std::memory_order_relaxed);
if (fd < 0) {
return;
}
char header[256];
const pid_t tid = static_cast<pid_t>(syscall(SYS_gettid));
const int header_len = std::snprintf(header, sizeof(header),
"=== UI stall backtrace (signal=%d pid=%d tid=%d) ===\n",
sig, getpid(), tid);
if (header_len > 0) {
write(fd, header, header_len);
}
void *frames[128];
const int frame_count = backtrace(frames, 128);
backtrace_symbols_fd(frames, frame_count, fd);
write(fd, "\n", 1);
}
void ui_stall_progress(UIStallPhase phase, uint64_t frame = 0) {
const uint64_t now = nanos_since_boot();
ui_stall_phase.store(static_cast<int>(phase), std::memory_order_relaxed);
ui_stall_frame.store(frame, std::memory_order_relaxed);
ui_stall_last_progress_ns.store(now, std::memory_order_relaxed);
if (ui_stall_reported.exchange(false, std::memory_order_relaxed)) {
const uint64_t stall_started = ui_stall_reported_ns.load(std::memory_order_relaxed);
const UIStallPhase stalled_phase = static_cast<UIStallPhase>(ui_stall_reported_phase.load(std::memory_order_relaxed));
const double stalled_for_s = stall_started == 0 ? 0.0 : (now - stall_started) / 1e9;
LOGW("UI stall recovered after %.1fs (stalled_phase=%s current_phase=%s frame=%llu)",
stalled_for_s,
ui_stall_phase_name(stalled_phase),
ui_stall_phase_name(phase),
static_cast<unsigned long long>(frame));
}
}
void start_ui_stall_monitor() {
static std::once_flag once;
std::call_once(once, [] {
ui_main_thread = pthread_self();
std::signal(SIGUSR1, ui_stall_signal_handler);
ui_stall_progress(UIStallPhase::INIT, 0);
const double stall_probe_dt = read_env_double("UI_STALL_PROBE_MAX_DT", 5.0);
if (stall_probe_dt <= 0.0) {
return;
}
std::thread([stall_probe_dt]() {
using namespace std::chrono_literals;
constexpr auto poll_interval = 250ms;
while (true) {
std::this_thread::sleep_for(poll_interval);
const uint64_t now = nanos_since_boot();
const uint64_t last_progress = ui_stall_last_progress_ns.load(std::memory_order_relaxed);
if (last_progress == 0) {
continue;
}
const double stalled_for_s = (now - last_progress) / 1e9;
if (stalled_for_s < stall_probe_dt) {
continue;
}
bool expected = false;
if (!ui_stall_reported.compare_exchange_strong(expected, true, std::memory_order_relaxed)) {
continue;
}
const UIStallPhase phase = static_cast<UIStallPhase>(ui_stall_phase.load(std::memory_order_relaxed));
const uint64_t frame = ui_stall_frame.load(std::memory_order_relaxed);
ui_stall_reported_ns.store(now, std::memory_order_relaxed);
ui_stall_reported_phase.store(static_cast<int>(phase), std::memory_order_relaxed);
const std::string path = ui_stall_dump_dir() + "/qt_ui_stall_" + std::to_string(getpid()) + "_" + std::to_string(now) + ".log";
int fd = open(path.c_str(), O_CREAT | O_WRONLY | O_TRUNC | O_CLOEXEC, 0644);
if (fd >= 0) {
char header[256];
const int header_len = std::snprintf(header, sizeof(header),
"phase=%s frame=%llu stalled_for_s=%.3f\n",
ui_stall_phase_name(phase),
static_cast<unsigned long long>(frame),
stalled_for_s);
if (header_len > 0) {
write(fd, header, header_len);
}
ui_stall_dump_fd.store(fd, std::memory_order_relaxed);
pthread_kill(ui_main_thread, SIGUSR1);
std::this_thread::sleep_for(50ms);
ui_stall_dump_fd.store(-1, std::memory_order_relaxed);
close(fd);
LOGE("UI main thread stalled for %.1fs (phase=%s frame=%llu dump=%s)",
stalled_for_s,
ui_stall_phase_name(phase),
static_cast<unsigned long long>(frame),
path.c_str());
} else {
LOGE("UI main thread stalled for %.1fs (phase=%s frame=%llu dump_open_failed errno=%d)",
stalled_for_s,
ui_stall_phase_name(phase),
static_cast<unsigned long long>(frame),
errno);
}
}
}).detach();
});
}
} // namespace
static void update_sockets(UIState *s) {
s->sm->update(0);
}
@@ -144,6 +330,7 @@ void UIState::updateStatus(StarPilotUIState *fs) {
}
UIState::UIState(QObject *parent) : QObject(parent) {
start_ui_stall_monitor();
sm = std::make_unique<SubMaster>(std::vector<const char*>{
"modelV2", "controlsState", "liveCalibration", "radarState", "deviceState",
"pandaStates", "carParams", "driverMonitoringState", "carState", "driverStateV2",
@@ -156,17 +343,26 @@ UIState::UIState(QObject *parent) : QObject(parent) {
timer = new QTimer(this);
QObject::connect(timer, &QTimer::timeout, this, &UIState::update);
timer->start(1000 / UI_FREQ);
ui_stall_progress(UIStallPhase::IDLE, sm->frame);
}
void UIState::update() {
ui_stall_progress(UIStallPhase::UPDATE_START, sm->frame);
update_sockets(this);
ui_stall_progress(UIStallPhase::AFTER_SOCKETS, sm->frame);
update_state(this, starpilotUIState());
ui_stall_progress(UIStallPhase::AFTER_STATE, sm->frame);
updateStatus(starpilotUIState());
ui_stall_progress(UIStallPhase::AFTER_STATUS, sm->frame);
if (sm->frame % UI_FREQ == 0) {
watchdog_kick(nanos_since_boot());
if (!watchdog_kick(nanos_since_boot())) {
LOGE("UI watchdog kick failed at frame %llu", static_cast<unsigned long long>(sm->frame));
}
}
ui_stall_progress(UIStallPhase::AFTER_WATCHDOG, sm->frame);
emit uiUpdate(*this, *starpilotUIState());
ui_stall_progress(UIStallPhase::AFTER_EMIT, sm->frame);
StarPilotUIState *fs = starpilotUIState();
StarPilotUIScene &starpilot_scene = fs->starpilot_scene;
@@ -177,6 +373,8 @@ void UIState::update() {
}
fs->update();
ui_stall_progress(UIStallPhase::AFTER_FS_UPDATE, sm->frame);
ui_stall_progress(UIStallPhase::IDLE, sm->frame);
}
Device::Device(QObject *parent) : brightness_filter(BACKLIGHT_OFFROAD, BACKLIGHT_TS, BACKLIGHT_DT), QObject(parent) {
+27 -11
View File
@@ -6,6 +6,7 @@ from openpilot.system.hardware import TICI
from openpilot.common.realtime import config_realtime_process, set_core_affinity
from openpilot.common.watchdog import kick_watchdog
from openpilot.system.ui.lib.application import gui_app
from openpilot.selfdrive.ui.stall_monitor import UIStallMonitor
from openpilot.selfdrive.ui.layouts.main import MainLayout
from openpilot.selfdrive.ui.mici.layouts.main import MiciMainLayout
from openpilot.selfdrive.ui.ui_state import ui_state
@@ -21,18 +22,33 @@ def main():
else:
main_layout = MiciMainLayout()
main_layout.set_rect(rl.Rectangle(0, 0, gui_app.width, gui_app.height))
for should_render in gui_app.render():
kick_watchdog()
ui_state.update()
if should_render:
main_layout.render()
stall_monitor = UIStallMonitor("raylib_ui")
gui_app.set_progress_hook(stall_monitor.progress)
stall_monitor.progress("ui.loop_ready")
stall_monitor.start()
# reaffine after power save offlines our core
if TICI and os.sched_getaffinity(0) != cores:
try:
set_core_affinity(list(cores))
except OSError:
pass
try:
for should_render in gui_app.render():
stall_monitor.progress("ui.loop_iteration")
kick_watchdog()
stall_monitor.progress("ui.after_watchdog")
ui_state.update()
stall_monitor.progress("ui.after_state_update")
if should_render:
stall_monitor.progress("ui.before_layout_render")
main_layout.render()
stall_monitor.progress("ui.after_layout_render")
# reaffine after power save offlines our core
if TICI and os.sched_getaffinity(0) != cores:
try:
set_core_affinity(list(cores))
except OSError:
pass
stall_monitor.progress("ui.loop_idle")
finally:
gui_app.set_progress_hook(None)
stall_monitor.stop()
if __name__ == "__main__":
+25
View File
@@ -224,6 +224,7 @@ class GuiApplication:
self._frame = 0
self._window_close_requested = False
self._trace_log_callback = None
self._progress_hook: Callable[[str], None] | None = None
self._modal_overlay = ModalOverlay()
self._modal_overlay_shown = False
self._modal_overlay_tick: Callable[[], None] | None = None
@@ -261,6 +262,18 @@ class GuiApplication:
def request_close(self):
self._window_close_requested = True
def set_progress_hook(self, hook: Callable[[str], None] | None):
self._progress_hook = hook
def _mark_progress(self, phase: str):
if self._progress_hook is None:
return
try:
self._progress_hook(phase)
except Exception:
pass
def init_window(self, title: str, fps: int = _DEFAULT_FPS):
with self._startup_profile_context():
@@ -536,6 +549,7 @@ class GuiApplication:
self._render_profiler.enable()
while not (self._window_close_requested or rl.window_should_close()):
self._mark_progress("gui_app.loop_start")
if PC:
# Thread is not used on PC, need to manually add mouse events
self._mouse._handle_mouse_event()
@@ -547,6 +561,7 @@ class GuiApplication:
# Skip rendering when screen is off
if not self._should_render:
self._mark_progress("gui_app.skip_render")
if PC:
rl.poll_input_events()
time.sleep(1 / self._target_fps)
@@ -554,25 +569,32 @@ class GuiApplication:
continue
if self._render_texture:
self._mark_progress("gui_app.begin_texture_mode")
rl.begin_texture_mode(self._render_texture)
rl.clear_background(rl.BLACK)
else:
self._mark_progress("gui_app.begin_drawing")
rl.begin_drawing()
rl.clear_background(rl.BLACK)
# Handle modal overlay rendering and input processing
if self._render_nav_stack():
self._mark_progress("gui_app.nav_stack")
yield False
elif self._handle_modal_overlay():
# Allow a Widget to still run a function while overlay is shown
if self._modal_overlay_tick is not None:
self._modal_overlay_tick()
self._mark_progress("gui_app.modal_overlay")
yield False
else:
self._mark_progress("gui_app.frame_ready")
yield True
if self._render_texture:
self._mark_progress("gui_app.end_texture_mode")
rl.end_texture_mode()
self._mark_progress("gui_app.begin_present")
rl.begin_drawing()
rl.clear_background(rl.BLACK)
src_rect = rl.Rectangle(0, 0, float(self._width), -float(self._height))
@@ -595,7 +617,9 @@ class GuiApplication:
if self._grid_size > 0:
self._draw_grid()
self._mark_progress("gui_app.end_drawing")
rl.end_drawing()
self._mark_progress("gui_app.after_end_drawing")
if RECORD:
image = rl.load_image_from_texture(self._render_texture.texture)
@@ -607,6 +631,7 @@ class GuiApplication:
self._monitor_fps()
self._frame += 1
self._mark_progress("gui_app.frame_complete")
if self._profile_render_frames > 0 and self._frame >= self._profile_render_frames:
self._output_render_profile()