TGViewer
C++ - Reddit C++ - Reddit @r_cpp · 230 subscribers
Post #25491 8
Tired of mutex hell and spaghetti event handlers? Here's a single-threaded FSM methodology that keeps your async logic clean — forever. Just #include "uniflow.hpp".

We've all been there. A module that starts simple —
connect, send, wait for ack — and six months later looks like this:

bool connecting_, connected_, cmd_sent_, waiting_ack_, draining_, fault_;

void Update() {
if (estop_) {
connecting_ = false;
cmd_sent_ = false;
waiting_ack_ = false;
// forgot draining_ — ghost ack bug waiting to happen
...
}
if (!connected_) { ... }
else if (!cmd_sent_) { ... }
else if (waiting_ack_) { ... }
// where does fault_ get handled? what if estop_ and fault_ fire together?
}

Flags that must move in pairs. Resets that get forgotten.
E-stop logic that has to know about every stage.
Two developers write the same flow in completely different shapes.

**uniflow** keeps what works about the tick-based FSM — cooperative,
single-thread, no mutex — and fixes what doesn't.

Each stage becomes a named step function:

StepResult Step1_Connect() { device_.BeginConnect();
return Next(UF_FN(Step2_WaitConnected)); }

StepResult Step2_WaitConnected() { if (!device_.IsConnected()) return Stay();
return Next(UF_FN(Step3_WaitRequest)); }

StepResult Step3_WaitRequest() { if (!input_.HasRequest()) return Stay();
device_.Send(input_.Take());
return Next(UF_FN(Step4_WaitAck)); }

StepResult Step4_WaitAck() { if (device_.HasAck()) return Done();
return StayUntil(3000ms, UF_FN(Step5_Timeout)); }

One function = one state. Entry is explicit. Transitions are pinned in code.
No hidden jumps. No forgotten resets. Brace depth stays flat forever.

---

**How it works**

One `Runtime` owns one pump thread. Attach as many modules as you want.
The pump visits each module once per round — cooperative, round-robin.

uniflow::Runtime rt;
Flow_XAxis x_axis{rt};
Flow_YAxis y_axis{rt};
Flow_Conveyor conveyor{rt};

x_axis.ctx_home_.StartFlow(); // start homing X
y_axis.ctx_home_.StartFlow(); // start homing Y — simultaneously, no thread, no mutex

Modules on the same Runtime share the single-thread invariant.
No locks needed. Ever.

The pump adapts its sleep to the situation:
- Back-to-back transitions? No sleep, full speed.
- Polling with Stay()? 20ms yield. CPU near 0.
- All idle? 1ms — ready to wake immediately.

External event arrives? Call `rt.Wake()` from any thread. No waiting out a sleep cycle.

---

**Blocking work? No problem.**

Heavy I/O goes to the built-in thread pool via SubmitAsync.
The pump never blocks. Every other module keeps running.

StepResult Step1_Fetch() {
AsyncId job = SubmitAsync(UF_FN(DoFetch), 5000ms, url_);
return Next(UF_FN(Step2_Process), job);
}

StepResult Step2_Process(AsyncId job) {
auto r = AsyncResult<std::string>(job);
if (r.pending()) return StayUntil(5000ms, UF_FN(Step_Timeout));
if (!r.ok()) return Fail();
data_ = *r.return_value;
return Next(UF_FN(Step3_Save));
}

---

**Built-in tracing — zero instrumentation code**

Because every execution is "a step function was called once,"
one measurement point inside the pump sees everything:

[JobWorker] FLOW START
[JobWorker] Entry -> Step2_Validate elapsed=0.01ms
[JobWorker] ASYNC SUBMIT CallApi
[JobWorker] ASYNC DONE CallApi wait=124.38ms
[JobWorker] Step2_Validate -> Step3_WaitSave elapsed=124.42ms
[JobWorker] FLOW END DONE wall=143.21ms

Which step, how long, where it slowed down — visible without touching your logic code.

---

**What it's not**

- Not Boost.Asio — no new type system to learn, zero deps, doesn't absorb your objects
- Not C++20 coroutines — C++17, and style is
More from @r_cpp
  1. Sep 29, 2026Boost.Graph 1.95 will be C++17 Dear Boost.Graph community, In two release cycles (Boost re…
  2. Sep 26, 2026Token Sequence Injection & Modern Macros: The Most Game-Changing Compile-Time Feature in C…
  3. Sep 25, 2026myStringStream.str("") Considered Harmful Under C++20 I was recently looking at some (rath…
  4. Sep 20, 2026A clever branch free optimization I'm the developer of memlz which is an extremely fast co…
  5. Sep 15, 2026Inside Boost.PolyCollection https://bannalia.blogspot.com/2026/09/inside-boostpolycollecti…
  6. Sep 11, 2026C++26: Standard Library Hardening Experiments https://www.cppstories.com/2026/hardening-ex…
Threads Profile ViewerView any public Threads profile without an account.Open ThreadLook →Writing with AI? Make it sound human.Metric37 rewrites AI drafts so they read naturally. Free AI detector, 1,500 words free.Try Metric37 →