Timer System Refactor: Stopwatch-like semantics and mcrfpy.timers collection #173

Closed
opened 2026-01-03 23:59:32 +00:00 by john · 0 comments
Owner

Summary

Refactor the Timer system to provide cleaner, more intuitive semantics before v1.0 release. The current timer implementation has confusing state management where cancelled/one-shot timers become unusable husks.

Current Problems

  1. Irreversible cancelled state: cancel() destroys the callback and resets the shared_ptr, making the timer object useless
  2. One-shot timers auto-destroy: After firing once, the timer can't be restarted
  3. No timer enumeration: Can't access mcrfpy.timers like we can mcrfpy.scenes
  4. active is read-only: Can't use timer.active = True to start/resume
  5. No start=False option: Can't create a timer in stopped state
  6. Dual APIs: Both setTimer()/delTimer() and Timer() objects exist

Proposed Design

New State Machine

  • Running: Timer is in engine map, ticking toward next fire
  • Paused: Timer is in engine map but frozen (preserves remaining time)
  • Stopped: Timer is NOT in engine map, progress reset, but callback preserved

Key Principle

Decouple "is timer in engine tick loop" from "does timer data exist". A stopped timer keeps its callback and can be restarted.

API Changes

# New init parameter
timer = mcrfpy.Timer("my_timer", callback, 1000, start=False)  # Created but not running

# Read-write active property
timer.active = True   # Start/resume
timer.active = False  # Pause

# New methods
timer.start()    # Add to engine, reset progress, begin running
timer.stop()     # Remove from engine, reset progress, preserve callback
timer.pause()    # Freeze in place (existing)
timer.resume()   # Continue from pause (existing)
timer.restart()  # Reset progress and ensure running (existing, enhanced)

# Timer collection
for t in mcrfpy.timers:
    print(f"{t.name}: active={t.active}")

Implementation Details

  1. Timer class gains stopped flag - replaces "callback is None" as removal signal
  2. stop() preserves callback - only removes from map and sets stopped=true
  3. start() adds to map - handles name collision by replacing existing timer
  4. testTimers() checks stopped flag - removes stopped timers from map
  5. One-shot timers set stopped=true - don't clear callback, can be restarted
  6. Remove setTimer/delTimer - v1.0 cleanup, not deprecation

Files to Modify

  • src/Timer.h - Add stopped flag
  • src/Timer.cpp - Update stop/start/test methods
  • src/PyTimer.h - Add start(), set_active(), update docstrings
  • src/PyTimer.cpp - Implement new semantics
  • src/McRFPy_API.cpp - Add timers collection, remove setTimer/delTimer
  • src/GameEngine.cpp - Update testTimers() cleanup logic
  • tests/ - Update test suite for new behavior

This is pre-v1.0 API cleanup work.

## Summary Refactor the Timer system to provide cleaner, more intuitive semantics before v1.0 release. The current timer implementation has confusing state management where cancelled/one-shot timers become unusable husks. ## Current Problems 1. **Irreversible cancelled state**: `cancel()` destroys the callback and resets the shared_ptr, making the timer object useless 2. **One-shot timers auto-destroy**: After firing once, the timer can't be restarted 3. **No timer enumeration**: Can't access `mcrfpy.timers` like we can `mcrfpy.scenes` 4. **`active` is read-only**: Can't use `timer.active = True` to start/resume 5. **No `start=False` option**: Can't create a timer in stopped state 6. **Dual APIs**: Both `setTimer()`/`delTimer()` and `Timer()` objects exist ## Proposed Design ### New State Machine - **Running**: Timer is in engine map, ticking toward next fire - **Paused**: Timer is in engine map but frozen (preserves remaining time) - **Stopped**: Timer is NOT in engine map, progress reset, but callback preserved ### Key Principle Decouple "is timer in engine tick loop" from "does timer data exist". A stopped timer keeps its callback and can be restarted. ### API Changes ```python # New init parameter timer = mcrfpy.Timer("my_timer", callback, 1000, start=False) # Created but not running # Read-write active property timer.active = True # Start/resume timer.active = False # Pause # New methods timer.start() # Add to engine, reset progress, begin running timer.stop() # Remove from engine, reset progress, preserve callback timer.pause() # Freeze in place (existing) timer.resume() # Continue from pause (existing) timer.restart() # Reset progress and ensure running (existing, enhanced) # Timer collection for t in mcrfpy.timers: print(f"{t.name}: active={t.active}") ``` ### Implementation Details 1. **Timer class gains `stopped` flag** - replaces "callback is None" as removal signal 2. **`stop()` preserves callback** - only removes from map and sets stopped=true 3. **`start()` adds to map** - handles name collision by replacing existing timer 4. **`testTimers()` checks stopped flag** - removes stopped timers from map 5. **One-shot timers set stopped=true** - don't clear callback, can be restarted 6. **Remove setTimer/delTimer** - v1.0 cleanup, not deprecation ## Files to Modify - `src/Timer.h` - Add stopped flag - `src/Timer.cpp` - Update stop/start/test methods - `src/PyTimer.h` - Add start(), set_active(), update docstrings - `src/PyTimer.cpp` - Implement new semantics - `src/McRFPy_API.cpp` - Add timers collection, remove setTimer/delTimer - `src/GameEngine.cpp` - Update testTimers() cleanup logic - `tests/` - Update test suite for new behavior ## Related Issues This is pre-v1.0 API cleanup work.
john closed this issue 2026-01-04 00:21:11 +00:00
Sign in to join this conversation.
No milestone
No project
No assignees
1 participant
Notifications
Due date
The due date is invalid or out of range. Please use the format "yyyy-mm-dd".

No due date set.

Dependencies

No dependencies set.

Reference
john/McRogueFace#173
No description provided.