Timer refactor: stopwatch-like semantics, mcrfpy.timers collection closes #173
Major Timer API improvements: - Add `stopped` flag to Timer C++ class for proper state management - Add `start()` method to restart stopped timers (preserves callback) - Add `stop()` method that removes from engine but preserves callback - Make `active` property read-write (True=start/resume, False=pause) - Add `start=True` init parameter to create timers in stopped state - Add `mcrfpy.timers` module-level collection (tuple of active timers) - One-shot timers now set stopped=true instead of clearing callback - Remove deprecated `setTimer()` and `delTimer()` module functions Timer callbacks now receive (timer, runtime) instead of just (runtime). Updated all tests to use new Timer API and callback signature. 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude Opus 4.5 <noreply@anthropic.com>
This commit is contained in:
parent
fc95fc2844
commit
5d41292bf6
16 changed files with 440 additions and 262 deletions
|
|
@ -1,70 +1,126 @@
|
|||
#!/usr/bin/env python3
|
||||
"""Test for mcrfpy.setTimer() and delTimer() methods"""
|
||||
"""Test for mcrfpy.Timer class - replaces old setTimer/delTimer tests (#173)"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
||||
def test_timers():
|
||||
"""Test timer API methods"""
|
||||
print("Testing mcrfpy timer methods...")
|
||||
|
||||
"""Test Timer class API"""
|
||||
print("Testing mcrfpy.Timer class...")
|
||||
|
||||
# Test 1: Create a simple timer
|
||||
try:
|
||||
call_count = [0]
|
||||
def simple_callback(runtime):
|
||||
def simple_callback(timer, runtime):
|
||||
call_count[0] += 1
|
||||
print(f"Timer callback called, count={call_count[0]}, runtime={runtime}")
|
||||
|
||||
mcrfpy.setTimer("test_timer", simple_callback, 100)
|
||||
print("✓ setTimer() called successfully")
|
||||
|
||||
timer = mcrfpy.Timer("test_timer", simple_callback, 100)
|
||||
print("✓ Timer() created successfully")
|
||||
print(f" Timer repr: {timer}")
|
||||
except Exception as e:
|
||||
print(f"✗ setTimer() failed: {e}")
|
||||
print(f"✗ Timer() failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 2: Delete the timer
|
||||
|
||||
# Test 2: Stop the timer
|
||||
try:
|
||||
mcrfpy.delTimer("test_timer")
|
||||
print("✓ delTimer() called successfully")
|
||||
timer.stop()
|
||||
print("✓ timer.stop() called successfully")
|
||||
assert timer.stopped == True, "Timer should be stopped"
|
||||
print(f" Timer after stop: {timer}")
|
||||
except Exception as e:
|
||||
print(f"✗ delTimer() failed: {e}")
|
||||
print(f"✗ timer.stop() failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 3: Delete non-existent timer (should not crash)
|
||||
|
||||
# Test 3: Restart the timer
|
||||
try:
|
||||
mcrfpy.delTimer("nonexistent_timer")
|
||||
print("✓ delTimer() accepts non-existent timer names")
|
||||
timer.start()
|
||||
print("✓ timer.start() called successfully")
|
||||
assert timer.stopped == False, "Timer should not be stopped"
|
||||
assert timer.active == True, "Timer should be active"
|
||||
timer.stop() # Clean up
|
||||
except Exception as e:
|
||||
print(f"✗ delTimer() failed on non-existent timer: {e}")
|
||||
print(f"✗ timer.start() failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 4: Create multiple timers
|
||||
|
||||
# Test 4: Create timer with start=False
|
||||
try:
|
||||
def callback1(rt): pass
|
||||
def callback2(rt): pass
|
||||
def callback3(rt): pass
|
||||
|
||||
mcrfpy.setTimer("timer1", callback1, 500)
|
||||
mcrfpy.setTimer("timer2", callback2, 750)
|
||||
mcrfpy.setTimer("timer3", callback3, 250)
|
||||
def callback2(timer, runtime): pass
|
||||
timer2 = mcrfpy.Timer("timer2", callback2, 500, start=False)
|
||||
assert timer2.stopped == True, "Timer with start=False should be stopped"
|
||||
print("✓ Timer with start=False created in stopped state")
|
||||
timer2.start()
|
||||
assert timer2.active == True, "Timer should be active after start()"
|
||||
timer2.stop()
|
||||
except Exception as e:
|
||||
print(f"✗ Timer with start=False failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 5: Create multiple timers
|
||||
try:
|
||||
def callback3(t, rt): pass
|
||||
|
||||
t1 = mcrfpy.Timer("multi1", callback3, 500)
|
||||
t2 = mcrfpy.Timer("multi2", callback3, 750)
|
||||
t3 = mcrfpy.Timer("multi3", callback3, 250)
|
||||
print("✓ Multiple timers created successfully")
|
||||
|
||||
|
||||
# Clean up
|
||||
mcrfpy.delTimer("timer1")
|
||||
mcrfpy.delTimer("timer2")
|
||||
mcrfpy.delTimer("timer3")
|
||||
print("✓ Multiple timers deleted successfully")
|
||||
t1.stop()
|
||||
t2.stop()
|
||||
t3.stop()
|
||||
print("✓ Multiple timers stopped successfully")
|
||||
except Exception as e:
|
||||
print(f"✗ Multiple timer test failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
print("\nAll timer API tests passed")
|
||||
|
||||
# Test 6: mcrfpy.timers collection
|
||||
try:
|
||||
# Create a timer that's running
|
||||
running_timer = mcrfpy.Timer("running_test", callback3, 1000)
|
||||
|
||||
timers = mcrfpy.timers
|
||||
assert isinstance(timers, tuple), "mcrfpy.timers should be a tuple"
|
||||
print(f"✓ mcrfpy.timers returns tuple with {len(timers)} timer(s)")
|
||||
|
||||
# Clean up
|
||||
running_timer.stop()
|
||||
except Exception as e:
|
||||
print(f"✗ mcrfpy.timers test failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
# Test 7: active property is read-write
|
||||
try:
|
||||
active_timer = mcrfpy.Timer("active_test", callback3, 1000)
|
||||
assert active_timer.active == True, "New timer should be active"
|
||||
|
||||
active_timer.active = False # Should pause
|
||||
assert active_timer.paused == True, "Timer should be paused after active=False"
|
||||
|
||||
active_timer.active = True # Should resume
|
||||
assert active_timer.active == True, "Timer should be active after active=True"
|
||||
|
||||
active_timer.stop()
|
||||
active_timer.active = True # Should restart from stopped
|
||||
assert active_timer.active == True, "Timer should restart from stopped via active=True"
|
||||
|
||||
active_timer.stop()
|
||||
print("✓ active property is read-write")
|
||||
except Exception as e:
|
||||
print(f"✗ active property test failed: {e}")
|
||||
print("FAIL")
|
||||
return
|
||||
|
||||
print("\nAll Timer API tests passed")
|
||||
print("PASS")
|
||||
|
||||
# Run the test
|
||||
test_timers()
|
||||
|
||||
# Exit cleanly
|
||||
sys.exit(0)
|
||||
sys.exit(0)
|
||||
|
|
|
|||
|
|
@ -68,9 +68,7 @@ def test_cell_hover():
|
|||
automation.moveTo(150, 150)
|
||||
automation.moveTo(200, 200)
|
||||
|
||||
def check_hover(runtime):
|
||||
mcrfpy.delTimer("check_hover")
|
||||
|
||||
def check_hover(timer, runtime):
|
||||
print(f" Enter events: {len(enter_events)}, Exit events: {len(exit_events)}")
|
||||
print(f" Hovered cell: {grid.hovered_cell}")
|
||||
|
||||
|
|
@ -82,7 +80,7 @@ def test_cell_hover():
|
|||
# Continue to click test
|
||||
test_cell_click()
|
||||
|
||||
mcrfpy.setTimer("check_hover", check_hover, 200)
|
||||
mcrfpy.Timer("check_hover", check_hover, 200, once=True)
|
||||
|
||||
|
||||
def test_cell_click():
|
||||
|
|
@ -105,9 +103,7 @@ def test_cell_click():
|
|||
|
||||
automation.click(200, 200)
|
||||
|
||||
def check_click(runtime):
|
||||
mcrfpy.delTimer("check_click")
|
||||
|
||||
def check_click(timer, runtime):
|
||||
print(f" Click events: {len(click_events)}")
|
||||
|
||||
if len(click_events) >= 1:
|
||||
|
|
@ -118,7 +114,7 @@ def test_cell_click():
|
|||
print("\n=== All grid cell event tests passed! ===")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.setTimer("check_click", check_click, 200)
|
||||
mcrfpy.Timer("check_click", check_click, 200, once=True)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
|||
|
|
@ -36,9 +36,7 @@ def test_headless_click():
|
|||
automation.click(150, 150)
|
||||
|
||||
# Give time for events to process
|
||||
def check_results(runtime):
|
||||
mcrfpy.delTimer("check_click") # Clean up timer
|
||||
|
||||
def check_results(timer, runtime):
|
||||
if len(start_clicks) >= 1:
|
||||
print(f" - Click received: {len(start_clicks)} click(s)")
|
||||
# Verify position
|
||||
|
|
@ -53,7 +51,7 @@ def test_headless_click():
|
|||
print(f" - No clicks received: FAIL")
|
||||
sys.exit(1)
|
||||
|
||||
mcrfpy.setTimer("check_click", check_results, 200)
|
||||
mcrfpy.Timer("check_click", check_results, 200, once=True)
|
||||
|
||||
|
||||
def test_click_miss():
|
||||
|
|
@ -84,9 +82,7 @@ def test_click_miss():
|
|||
print(" Clicking outside frame at (50, 50)...")
|
||||
automation.click(50, 50)
|
||||
|
||||
def check_miss_results(runtime):
|
||||
mcrfpy.delTimer("check_miss") # Clean up timer
|
||||
|
||||
def check_miss_results(timer, runtime):
|
||||
if miss_count[0] == 0:
|
||||
print(" - No click on miss: PASS")
|
||||
# Now run the main click test
|
||||
|
|
@ -95,7 +91,7 @@ def test_click_miss():
|
|||
print(f" - Unexpected {miss_count[0]} click(s): FAIL")
|
||||
sys.exit(1)
|
||||
|
||||
mcrfpy.setTimer("check_miss", check_miss_results, 200)
|
||||
mcrfpy.Timer("check_miss", check_miss_results, 200, once=True)
|
||||
|
||||
|
||||
def test_position_tracking():
|
||||
|
|
|
|||
|
|
@ -153,7 +153,7 @@ def test_enter_exit_simulation():
|
|||
automation.moveTo(50, 50)
|
||||
|
||||
# Give time for callbacks to execute
|
||||
def check_results(runtime):
|
||||
def check_results(timer, runtime):
|
||||
global enter_count, exit_count
|
||||
|
||||
if enter_count >= 1 and exit_count >= 1:
|
||||
|
|
@ -166,7 +166,7 @@ def test_enter_exit_simulation():
|
|||
print("\n=== Basic Mouse Enter/Exit tests passed! ===")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.setTimer("check", check_results, 200)
|
||||
mcrfpy.Timer("check", check_results, 200, once=True)
|
||||
|
||||
|
||||
def run_basic_tests():
|
||||
|
|
|
|||
|
|
@ -57,9 +57,7 @@ def test_on_move_fires():
|
|||
automation.moveTo(200, 200)
|
||||
automation.moveTo(250, 250)
|
||||
|
||||
def check_results(runtime):
|
||||
mcrfpy.delTimer("check_move")
|
||||
|
||||
def check_results(timer, runtime):
|
||||
if move_count[0] >= 2:
|
||||
print(f" - on_move fired {move_count[0]} times: PASS")
|
||||
print(f" Positions: {positions[:5]}...")
|
||||
|
|
@ -71,7 +69,7 @@ def test_on_move_fires():
|
|||
print("\n=== on_move basic tests passed! ===")
|
||||
sys.exit(0)
|
||||
|
||||
mcrfpy.setTimer("check_move", check_results, 200)
|
||||
mcrfpy.Timer("check_move", check_results, 200, once=True)
|
||||
|
||||
|
||||
def test_on_move_not_outside():
|
||||
|
|
@ -99,9 +97,7 @@ def test_on_move_not_outside():
|
|||
automation.moveTo(60, 60)
|
||||
automation.moveTo(70, 70)
|
||||
|
||||
def check_results(runtime):
|
||||
mcrfpy.delTimer("check_outside")
|
||||
|
||||
def check_results(timer, runtime):
|
||||
if move_count[0] == 0:
|
||||
print(" - No on_move outside bounds: PASS")
|
||||
# Chain to the firing test
|
||||
|
|
@ -110,7 +106,7 @@ def test_on_move_not_outside():
|
|||
print(f" - Unexpected {move_count[0]} move(s) outside bounds: FAIL")
|
||||
sys.exit(1)
|
||||
|
||||
mcrfpy.setTimer("check_outside", check_results, 200)
|
||||
mcrfpy.Timer("check_outside", check_results, 200, once=True)
|
||||
|
||||
|
||||
def test_all_types_have_on_move():
|
||||
|
|
|
|||
|
|
@ -63,13 +63,13 @@ def run_tests():
|
|||
print("Test 5: Timer fires after step() advances past interval")
|
||||
timer_fired = [False] # Use list for mutable closure
|
||||
|
||||
def on_timer(runtime):
|
||||
"""Timer callback - receives runtime in ms"""
|
||||
def on_timer(timer, runtime):
|
||||
"""Timer callback - receives timer object and runtime in ms"""
|
||||
timer_fired[0] = True
|
||||
print(f" Timer fired at simulation time={runtime}ms")
|
||||
|
||||
# Set a timer for 500ms
|
||||
mcrfpy.setTimer("test_timer", on_timer, 500)
|
||||
test_timer = mcrfpy.Timer("test_timer", on_timer, 500)
|
||||
|
||||
# Step 600ms - timer should fire (500ms interval + some buffer)
|
||||
dt = mcrfpy.step(0.6)
|
||||
|
|
@ -88,7 +88,7 @@ def run_tests():
|
|||
print(" Skipping timer test in windowed mode")
|
||||
|
||||
# Clean up
|
||||
mcrfpy.delTimer("test_timer")
|
||||
test_timer.stop()
|
||||
print()
|
||||
|
||||
# Test 6: Error handling - invalid argument type
|
||||
|
|
|
|||
|
|
@ -1,6 +1,7 @@
|
|||
#!/usr/bin/env python3
|
||||
"""
|
||||
Test once=True timer functionality
|
||||
Uses mcrfpy.step() to advance time in headless mode.
|
||||
"""
|
||||
import mcrfpy
|
||||
import sys
|
||||
|
|
@ -18,20 +19,8 @@ def repeat_callback(timer, runtime):
|
|||
repeat_count += 1
|
||||
print(f"Repeat timer fired! Count: {repeat_count}, Timer.once: {timer.once}")
|
||||
|
||||
def check_results(runtime):
|
||||
print(f"\nFinal results:")
|
||||
print(f"Once timer fired {once_count} times (expected: 1)")
|
||||
print(f"Repeat timer fired {repeat_count} times (expected: 3+)")
|
||||
|
||||
if once_count == 1 and repeat_count >= 3:
|
||||
print("PASS: Once timer fired exactly once, repeat timer fired multiple times")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("FAIL: Timer behavior incorrect")
|
||||
sys.exit(1)
|
||||
|
||||
# Set up the scene
|
||||
test_scene = mcrfpy.Scene("test_scene")
|
||||
test_scene = mcrfpy.Scene("test_scene")
|
||||
test_scene.activate()
|
||||
|
||||
# Create timers
|
||||
|
|
@ -43,5 +32,20 @@ print("\nCreating repeat timer with once=False (default)...")
|
|||
repeat_timer = mcrfpy.Timer("repeat_timer", repeat_callback, 100)
|
||||
print(f"Timer: {repeat_timer}, once={repeat_timer.once}")
|
||||
|
||||
# Check results after 500ms
|
||||
mcrfpy.setTimer("check", check_results, 500)
|
||||
# Advance time using step() to let timers fire
|
||||
# Step 600ms total - once timer (100ms) fires once, repeat timer fires ~6 times
|
||||
print("\nAdvancing time with step()...")
|
||||
for i in range(6):
|
||||
mcrfpy.step(0.1) # 100ms each
|
||||
|
||||
# Check results
|
||||
print(f"\nFinal results:")
|
||||
print(f"Once timer fired {once_count} times (expected: 1)")
|
||||
print(f"Repeat timer fired {repeat_count} times (expected: 3+)")
|
||||
|
||||
if once_count == 1 and repeat_count >= 3:
|
||||
print("PASS: Once timer fired exactly once, repeat timer fired multiple times")
|
||||
sys.exit(0)
|
||||
else:
|
||||
print("FAIL: Timer behavior incorrect")
|
||||
sys.exit(1)
|
||||
|
|
|
|||
|
|
@ -23,20 +23,28 @@ caption = mcrfpy.Caption(pos=(150, 150),
|
|||
caption.font_size = 24
|
||||
ui.append(caption)
|
||||
|
||||
# Timer callback with correct signature
|
||||
def timer_callback(runtime):
|
||||
# Timer callback with new signature (timer, runtime)
|
||||
def timer_callback(timer, runtime):
|
||||
print(f"\n✓ Timer fired successfully at runtime: {runtime}")
|
||||
|
||||
|
||||
# Take screenshot
|
||||
filename = f"timer_success_{int(runtime)}.png"
|
||||
result = automation.screenshot(filename)
|
||||
print(f"Screenshot saved: {filename} - Result: {result}")
|
||||
|
||||
# Cancel timer and exit
|
||||
mcrfpy.delTimer("success_timer")
|
||||
|
||||
# Stop timer and exit
|
||||
timer.stop()
|
||||
print("Exiting...")
|
||||
mcrfpy.exit()
|
||||
|
||||
# Set timer
|
||||
mcrfpy.setTimer("success_timer", timer_callback, 1000)
|
||||
print("Timer set for 1 second. Game loop starting...")
|
||||
# Create timer (new API)
|
||||
success_timer = mcrfpy.Timer("success_timer", timer_callback, 1000, once=True)
|
||||
print("Timer set for 1 second. Using step() to advance time...")
|
||||
|
||||
# In headless mode, advance time manually
|
||||
for i in range(11): # 1100ms total
|
||||
mcrfpy.step(0.1)
|
||||
|
||||
print("PASS")
|
||||
import sys
|
||||
sys.exit(0)
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue