diff --git a/epymorph/event.py b/epymorph/event.py index d9f905ae..2a665873 100644 --- a/epymorph/event.py +++ b/epymorph/event.py @@ -34,6 +34,7 @@ class OnTick(NamedTuple): tick_index: int percent_complete: float + dim: SimDimensions ################### diff --git a/epymorph/log/messaging.py b/epymorph/log/messaging.py index a8c5cf9f..a8fed637 100644 --- a/epymorph/log/messaging.py +++ b/epymorph/log/messaging.py @@ -6,9 +6,11 @@ from contextlib import contextmanager from functools import partial +from math import ceil from time import perf_counter from typing import Generator +import humanize from humanize import naturalsize from epymorph.event import AdrioProgress, EventBus, OnStart, OnTick @@ -39,20 +41,38 @@ def on_start(e: OnStart) -> None: nonlocal start_time start_time = perf_counter() + # keeping track of the length of the last line we printed + # lets us clear any trailing characters when rendering stuff + # after the progress bar of varying width + last_progress_length = 0 + def on_tick(tick: OnTick) -> None: - print(f" {progress(tick.percent_complete)}", end="\r") + nonlocal last_progress_length + ticks_complete = tick.tick_index + 1 + total_process_time = perf_counter() - start_time + average_process_time = total_process_time / ticks_complete + + ticks_left = tick.dim.ticks - ticks_complete + + # multiply the remaining ticks by the average processing time + estimate = ticks_left * average_process_time + + time_remaining = humanize.precisedelta(ceil(estimate), minimum_unit="seconds") + formatted_time = f"({time_remaining} remaining)" + line = f" {progress(tick.percent_complete)}" + # if no time remaining, omit the time progress + if estimate > 0: + line += f"{formatted_time}" + print(line.ljust(last_progress_length), end="\r") + last_progress_length = len(line) def on_finish(_: None) -> None: end_time = perf_counter() - print(f" {progress(1.0)}") + line = f" {progress(1.0)}" + print(line.ljust(last_progress_length), end="\n") if start_time is not None: print(f"Runtime: {(end_time - start_time):.3f}s") - # keeping track of the length of the last line we printed - # lets us clear any trailing characters when rendering stuff - # after the progress bar of varying width - last_progress_length = 0 - def on_adrio_progress(e: AdrioProgress) -> None: nonlocal last_progress_length if e.ratio_complete == 0: diff --git a/epymorph/simulator/basic/basic_simulator.py b/epymorph/simulator/basic/basic_simulator.py index 7cd15df5..732f2a41 100644 --- a/epymorph/simulator/basic/basic_simulator.py +++ b/epymorph/simulator/basic/basic_simulator.py @@ -128,7 +128,7 @@ def run( out.compartments[tick.sim_index] = tick_compartments t = tick.sim_index - _events.on_tick.publish(OnTick(t, (t + 1) / dim.ticks)) + _events.on_tick.publish(OnTick(t, (t + 1) / dim.ticks, dim)) _events.on_finish.publish(None)