Skip to main content

ui_messages_handle/
ui_messages_handle.rs

1//! Example: receive messages from a running sequence with no GUI attached.
2//!
3//! ```text
4//! cargo run --example ui_messages_handle
5//! ```
6//!
7//! A sequence reports what it is doing by posting user-interface messages. A
8//! graphical front end receives them through the UI controls; a headless host, //! a service, a test runner, anything forwarding to another process, polls the
9//! engine's queue instead. That polling is what this shows.
10
11use std::time::{Duration, Instant};
12
13use rs_teststand::{Engine, Sequence, StepGroup, UIMessageCode};
14
15const INSERT_IF_MISSING: i32 = 1;
16const NO_OPTIONS: i32 = 0;
17const NO_ADAPTER: &str = "";
18
19const STAGE_MESSAGE: i32 = UIMessageCode::USER_MESSAGE_BASE + 1;
20const PROGRESS_MESSAGE: i32 = UIMessageCode::USER_MESSAGE_BASE + 2;
21
22fn add_statement(
23    engine: &Engine,
24    sequence: &Sequence,
25    name: &str,
26    expression: &str,
27) -> Result<(), rs_teststand::Error> {
28    let step = engine.new_step(NO_ADAPTER, "Statement")?;
29    step.set_name(name)?;
30    step.as_property_object()?
31        .set_val_string("TS.PostExpr", INSERT_IF_MISSING, expression)?;
32    sequence.insert_step(
33        &step,
34        sequence.get_num_steps(StepGroup::Main)?,
35        StepGroup::Main,
36    )?;
37    Ok(())
38}
39
40fn main() -> Result<(), Box<dyn std::error::Error>> {
41    let engine = Engine::new()?;
42
43    // Nothing reaches the queue until polling is switched on.
44    engine.set_ui_message_polling_enabled(true)?;
45
46    let sequence_file = engine.new_sequence_file()?;
47    let main_sequence = sequence_file.get_sequence_by_name("MainSequence")?;
48
49    // Posted through the engine and tagged with the execution. Synchronous, so
50    // the sequence waits until the host acknowledges it.
51    add_statement(
52        &engine,
53        &main_sequence,
54        "Report Stage",
55        &format!(
56            "RunState.Engine.PostUIMessage(RunState.Execution, RunState.Thread, {STAGE_MESSAGE}, \
57             1, \"stage: configuring instruments\", Nothing, True)"
58        ),
59    )?;
60    // Posted through the thread. Asynchronous, so the sequence carries on.
61    add_statement(
62        &engine,
63        &main_sequence,
64        "Report Progress",
65        &format!(
66            "RunState.Thread.PostUIMessageEx({PROGRESS_MESSAGE}, 50, \"progress: halfway\", \
67             Nothing, False)"
68        ),
69    )?;
70
71    let execution = engine.new_execution(&sequence_file, "MainSequence", None, false, 0)?;
72    println!(
73        "Execution {} started; polling for messages...",
74        execution.id()?
75    );
76
77    // The end is detected from the message stream, not by waiting on the
78    // execution: a wait call does not pump the queue, so a synchronous message
79    // would sit unacknowledged and both sides would stop.
80    let started = Instant::now();
81    let mut ended = false;
82    while started.elapsed() < Duration::from_secs(60) && !ended {
83        while !engine.is_ui_message_queue_empty()? {
84            let message = engine.get_ui_message()?;
85            let code = message.event()?;
86            if matches!(
87                UIMessageCode::from_bits(code),
88                Ok(UIMessageCode::EndExecution)
89            ) {
90                ended = true;
91            }
92            let origin = if UIMessageCode::is_user_message(code) {
93                "sequence".to_owned()
94            } else {
95                format!("engine {:?}", UIMessageCode::from_bits(code))
96            };
97            println!(
98                "  [{origin}] code={code} numeric={} string={:?} synchronous={}",
99                message.numeric_data()?,
100                message.string_data()?,
101                message.is_synchronous()?
102            );
103            // Required: releases a synchronous poster and asks for the next.
104            message.acknowledge()?;
105        }
106    }
107
108    println!("\nExecution ended: {ended}");
109    engine.release_sequence_file_ex(sequence_file, NO_OPTIONS)?;
110    Ok(())
111}