File size: 6,985 Bytes
67fb03c
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
# 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 <elapsed_key> while <flag_key> 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()