feat(tick): add TimeClock for driver-free time retrieval#537
feat(tick): add TimeClock for driver-free time retrieval#537martintmk wants to merge 13 commits into
Conversation
Add Clock::new_thread_driven behind the new thread-driven feature. It spawns a dedicated background OS thread that periodically advances the clock's timers, requiring no async runtime or manual ClockDriver polling. The thread terminates once all Clock clones are dropped. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Codecov Report✅ All modified and coverable lines are covered by tests. Additional details and impacted files@@ Coverage Diff @@
## main #537 +/- ##
=======================================
Coverage 100.0% 100.0%
=======================================
Files 353 354 +1
Lines 26821 26887 +66
=======================================
+ Hits 26821 26887 +66 ☔ View full report in Codecov by Harness. 🚀 New features to boost your workflow:
|
Introduce TimeClock, a simplified clock exposing time retrieval only (no timers), requiring no runtime or driver via TimeClock::new_system/new_frozen/new_frozen_at. Clock implements AsRef<TimeClock> and exposes time_clock(); ClockControl gains to_time_clock(); Stopwatch::new now accepts impl AsRef<TimeClock> so both clock kinds work seamlessly. This supersedes and reverts the discouraged thread-driven clock constructor. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Add From<ClockControl>/From<&ClockControl> for TimeClock (parity with Clock) and From<Clock>/From<&Clock> for TimeClock for ergonomic downgrade. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
There was a problem hiding this comment.
Pull request overview
This PR introduces TimeClock, a new public, driver-free clock abstraction in tick for time retrieval only (no timers), and refactors existing time-read APIs to share this single source of truth. It updates Stopwatch to accept any AsRef<TimeClock> so callers can use either a full timer-capable Clock or a lightweight TimeClock interchangeably.
Changes:
- Add
TimeClock(new_system, controlled/frozen constructors undertest-util) and wire it intoClockas the delegated implementation forsystem_time,instant, andsystem_time_as. - Update
Stopwatchto store aTimeClock+ startInstant, and broadenStopwatch::new(...)to acceptimpl AsRef<TimeClock>. - Update crate-level docs and regenerated README to document “time retrieval without timers”.
Reviewed changes
Copilot reviewed 6 out of 7 changed files in this pull request and generated 3 comments.
Show a summary per file
| File | Description |
|---|---|
| crates/tick/src/time_clock.rs | New TimeClock type providing driver-free time retrieval and controlled/frozen variants for tests. |
| crates/tick/src/stopwatch.rs | Refactors Stopwatch to accept any AsRef<TimeClock> and simplifies internal representation. |
| crates/tick/src/lib.rs | Exposes TimeClock and documents the new “time retrieval without timers” concept. |
| crates/tick/src/clock.rs | Adds Clock::time_clock() and delegates time-read methods to the embedded TimeClock. |
| crates/tick/src/clock_control.rs | Adds ClockControl::to_time_clock() and From impls to create controlled TimeClocks. |
| crates/tick/README.md | Regenerated documentation reflecting the new TimeClock API and narrative. |
💡 Add Copilot custom instructions for smarter, more guided reviews. Learn how to get started.
| pub fn system_time_as<T: TryFrom<SystemTime>>(&self) -> T { | ||
| match T::try_from(self.system_time()) { | ||
| Ok(time) => time, | ||
| Err(_err) => { | ||
| panic!("The SystemTime returned by the clock is always in normalized range and must be convertible to the target type.",) | ||
| } | ||
| } | ||
| } |
| pub fn to_time_clock(&self) -> crate::TimeClock { | ||
| crate::TimeClock::from_state(&ClockState::ClockControl(self.clone())) | ||
| } |
| pub fn system_time_as<T: TryFrom<SystemTime>>(&self) -> T { | ||
| match T::try_from(self.system_time()) { | ||
| Ok(time) => time, | ||
| Err(_err) => { | ||
| panic!("The SystemTime returned by the clock is always in normalized range and must be convertible to the target type.",) | ||
| } |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Add TimeClock::from_control taking an owned ClockControl and use it from ClockControl::to_time_clock and From<ClockControl>, eliminating the extra Arc clone that went through a temporary ClockState. Also adds a Stopwatch regression test covering all AsRef<TimeClock> inputs. Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| match T::try_from(self.system_time()) { | ||
| Ok(time) => time, | ||
| Err(_err) => { | ||
| panic!("The SystemTime returned by the clock is always in normalized range and must be convertible to the target type.") | ||
| } | ||
| } |
| /// `test-util` feature), where time could be moved excessively far into the future, | ||
| /// potentially exceeding the target type's supported range of values. This is not a concern | ||
| /// in production. |
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot Autofix powered by AI <175728472+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
Co-authored-by: Copilot <223556219+Copilot@users.noreply.github.com>
| #[derive(Clone)] | ||
| pub struct Clock { | ||
| state: ClockState, | ||
| time: TimeClock, |
There was a problem hiding this comment.
TimeClock does not really English for me. Suggested name: TimeSource or just Time
| /// - The system time returned is always within a normalized range in real environments. | ||
| /// - Target types that implement [`TryFrom<SystemTime>`][TryFrom] typically support the full valid | ||
| /// range of system time values. | ||
| /// For types that support the full valid range of system time values, this conversion always |
There was a problem hiding this comment.
The phrase "full valid range of system time values" is confusing. Is "system time" the same as "SystemTime" or something different? What does the word "valid" mean - is there an "invalid range" of system time values that should also be accounted for? How do I know if a SystemTime instance is valid or invalid, if so?
| use crate::state::ClockState; | ||
| use crate::thread_aware_move; | ||
|
|
||
| /// A simplified clock used purely for **time retrieval**. |
There was a problem hiding this comment.
I suggest re-phrasing this type purely in terms of what it is for, instead of it being a "variant" of another thing. The variant-based description creates needless relationships the reader needs to resolve in their head.
There was a problem hiding this comment.
Well, the variant is really what this thing is. I don't want to introduce new word or concept. We have a "simple clock" that does just time retrieval and then clock that does time retrieval + timers handling. We just need to find a good name for it.
PlainClockSimpleClockBasicClock
(I am not happy about neither of these either)
Summary
Adds
TimeClock, a simplified clock used purely for time retrieval (no timers). UnlikeClock, it carries no timers, so it needs no async runtime and no driver —TimeClock::new_system()returns a ready-to-use clock backed by real OS time.TimeClockis the common abstraction shared by every clock kind, so time-only APIs (e.g.Stopwatch) accept either a fullClockor aTimeClockseamlessly.API
TimeClock(new public type):TimeClock::new_system()— driver-free clock backed by real OS time.TimeClock::new_frozen()/new_frozen_at(time)— frozen clocks (mirrorsClock, gated behindtest-util).system_time(),instant(),system_time_as::<T>(),stopwatch().AsRef<TimeClock>andThreadAware.Clock(unchanged public surface):AsRef<TimeClock>.Clock::time_clock() -> &TimeClockaccessor.TimeClock(single source of truth).ClockControl:ClockControl::to_time_clock()— controlledTimeClockdriven by the sameClockControlasto_clock(); both observe identical controlled time.Stopwatch:Stopwatch::new(impl AsRef<TimeClock>)— accepts&Clock,&TimeClock, or an ownedTimeClock. Existingclock.stopwatch()/Stopwatch::new(&clock)call sites compile unchanged via std's blanket&T: AsRef<U>.#[cfg]split is gone; production cost remains a singleInstant(TimeClock::Systemis zero-sized).Seamlessness
Docs
lib.rs+ Overview bullet.README.mdregenerated viajust readme.Testing
cargo test -p tick --all-features: 143 unit + 54 doctests pass.cargo clippy -p tick --all-features --all-targets -- -D warnings: clean.cargo doc -p tick --all-features: clean.just spellcheckpasses; formatted withjust format.