# trame_timer_full_demo_v2.py # Trame (Vuetify v2) – robust, reusable ticking timers for any long-running task import time import threading from trame.app import get_server from trame.ui.vuetify import SinglePageLayout from trame.widgets import vuetify as v # ----------------------------------------------------------------------------- # Server / State # ----------------------------------------------------------------------------- server = get_server() # Vuetify v2 stack state = server.state # Upload stage state.is_uploading = False state.upload_elapsed = 0.0 state.upload_progress = 0 # 0..100 state.upload_msg = "" # Predict stage state.is_predicting = False state.predict_elapsed = 0.0 state.predict_progress = 0 # 0..100 state.predict_msg = "" def _flush_safe(): try: state.flush() except Exception: # Prefer logging in real apps; avoid crashing worker thread pass # ----------------------------------------------------------------------------- # Generic, reusable timer helpers # ----------------------------------------------------------------------------- def start_ticker(flag_key: str, elapsed_key: str, interval_s: float = 0.1): """ Start a thread that updates while is True. """ state[elapsed_key] = 0.0 state[flag_key] = True _flush_safe() def _tick(): t0 = time.perf_counter() while state.get(flag_key, False): state[elapsed_key] = time.perf_counter() - t0 _flush_safe() time.sleep(interval_s) threading.Thread(target=_tick, daemon=True).start() def stop_ticker(flag_key: str): state[flag_key] = False _flush_safe() def run_with_timer(flag_key: str, elapsed_key: str, work_fn, *args, **kwargs): """ Fire-and-forget: starts ticker, runs work_fn(*args, **kwargs) in a thread, and always stops ticker in a finally-block. Returns the worker thread. """ start_ticker(flag_key, elapsed_key) def _runner(): try: work_fn(*args, **kwargs) finally: stop_ticker(flag_key) t = threading.Thread(target=_runner, daemon=True) t.start() return t # ----------------------------------------------------------------------------- # Example “work” functions (replace with your real code) # ----------------------------------------------------------------------------- def _simulate_upload_work(): """ Simulates an upload: increments progress 0..100 and sets a message. If cancelled (is_uploading False), it exits cleanly. """ state.upload_msg = "Uploading geometry..." state.upload_progress = 0 _flush_safe() for i in range(101): if not state.is_uploading: state.upload_msg = "Upload cancelled." _flush_safe() return state.upload_progress = i _flush_safe() time.sleep(0.05) # Simulate IO chunks state.upload_msg = "Upload completed." _flush_safe() def _simulate_predict_work(): """ Simulates prediction: increments progress 0..100 and sets a message. """ state.predict_msg = "Running prediction..." state.predict_progress = 0 _flush_safe() for i in range(101): if not state.is_predicting: state.predict_msg = "Prediction cancelled." _flush_safe() return state.predict_progress = i _flush_safe() time.sleep(0.06) # Simulate compute steps state.predict_msg = "Prediction completed." _flush_safe() # ----------------------------------------------------------------------------- # Controllers (button hooks) # ----------------------------------------------------------------------------- def start_upload(): # Reset UI bits for a fresh run state.upload_progress = 0 state.upload_msg = "" _flush_safe() run_with_timer("is_uploading", "upload_elapsed", _simulate_upload_work) def cancel_upload(): # Just drop the flag; worker and ticker will stop on next loop stop_ticker("is_uploading") def start_predict(): state.predict_progress = 0 state.predict_msg = "" _flush_safe() run_with_timer("is_predicting", "predict_elapsed", _simulate_predict_work) def cancel_predict(): stop_ticker("is_predicting") # ----------------------------------------------------------------------------- # UI (Vuetify v2) # ----------------------------------------------------------------------------- with SinglePageLayout(server) as layout: layout.title.set_text("Trame Live Timer – Upload & Predict") with layout.toolbar: v.VBtn("Start Upload", color="primary", click=start_upload) v.VBtn("Cancel Upload", color="error", click=cancel_upload, classes="ml-2") v.VDivider(vertical=True, classes="mx-4") v.VBtn("Start Predict", color="secondary", click=start_predict) v.VBtn("Cancel Predict", color="error", click=cancel_predict, classes="ml-2") with layout.content: # Upload panel v.VSheet( class_="pa-4 mx-auto", style="max-width: 600px; margin-top: 40px;" )( v.VSubheader("Upload Stage"), # Progress bar (determinate) – remove v_model & set indeterminate=True if duration unknown v.VProgressLinear( v_model=("upload_progress", 0), height=14, color="primary", rounded=True, v_show=("is_uploading",), ), v.VSpacer(style="min-height:10px;"), # Live elapsed time v.VLabel( "Elapsed: {{ upload_elapsed.toFixed(2) }} s", v_show=("is_uploading",), style="font-weight:600; font-size:14px; color:#1976d2;", ), v.VSpacer(style="min-height:6px;"), v.VLabel(("{{ upload_msg }}",), style="opacity:0.8;"), ) # Predict panel v.VSheet( class_="pa-4 mx-auto", style="max-width: 600px; margin-top: 20px;" )( v.VSubheader("Prediction Stage"), v.VProgressLinear( v_model=("predict_progress", 0), height=14, color="secondary", rounded=True, v_show=("is_predicting",), ), v.VSpacer(style="min-height:10px;"), v.VLabel( "Elapsed: {{ predict_elapsed.toFixed(2) }} s", v_show=("is_predicting",), style="font-weight:600; font-size:14px;", ), v.VSpacer(style="min-height:6px;"), v.VLabel(("{{ predict_msg }}",), style="opacity:0.8;"), ) # ----------------------------------------------------------------------------- # Run # ----------------------------------------------------------------------------- if __name__ == "__main__": server.start()