Compare commits
12 Commits
master
...
alpha_pres
Author | SHA1 | Date |
---|---|---|
|
c5e7e8e298 | |
|
6d29652ae7 | |
|
a010e5fa96 | |
|
9c8d6c4591 | |
|
dcd1b0ca33 | |
|
6813fb5129 | |
|
6f67fbb51e | |
|
eb88c7b3aa | |
|
9fb428dd01 | |
|
bde82028b5 | |
|
062e4dadc4 | |
|
98fc49a978 |
10
README.md
10
README.md
|
@ -64,11 +64,11 @@ For comprehensive documentation, tutorials, and API reference, visit:
|
||||||
|
|
||||||
The documentation site includes:
|
The documentation site includes:
|
||||||
|
|
||||||
- **[Quickstart Guide](https://mcrogueface.github.io/quickstart)** - Get running in 5 minutes
|
- **[Quickstart Guide](https://mcrogueface.github.io/quickstart/)** - Get running in 5 minutes
|
||||||
- **[McRogueFace Does The Entire Roguelike Tutorial](https://mcrogueface.github.io/tutorials)** - Step-by-step game building
|
- **[McRogueFace Does The Entire Roguelike Tutorial](https://mcrogueface.github.io/tutorials/)** - Step-by-step game building
|
||||||
- **[Complete API Reference](https://mcrogueface.github.io/api)** - Every function documented
|
- **[Complete API Reference](https://mcrogueface.github.io/api/)** - Every function documented
|
||||||
- **[Cookbook](https://mcrogueface.github.io/cookbook)** - Ready-to-use code recipes
|
- **[Cookbook](https://mcrogueface.github.io/cookbook/)** - Ready-to-use code recipes
|
||||||
- **[C++ Extension Guide](https://mcrogueface.github.io/extending-cpp)** - For C++ developers: Add engine features
|
- **[C++ Extension Guide](https://mcrogueface.github.io/extending-cpp/)** - For C++ developers: Add engine features
|
||||||
|
|
||||||
## Build Requirements
|
## Build Requirements
|
||||||
|
|
||||||
|
|
|
@ -0,0 +1,935 @@
|
||||||
|
# McRogueFace - Development Roadmap
|
||||||
|
|
||||||
|
## 🚨 URGENT PRIORITIES - July 12, 2025 🚨
|
||||||
|
|
||||||
|
### CRITICAL: RoguelikeDev Tutorial Event starts July 15! (3 days)
|
||||||
|
|
||||||
|
#### 1. Tutorial Status & Blockers
|
||||||
|
- [x] **Part 0**: Complete (Starting McRogueFace)
|
||||||
|
- [x] **Part 1**: Complete (Setting up grid and tile sheet)
|
||||||
|
- [ ] **Part 2**: Draft exists but BLOCKED by animation issues - PRIORITY FIX!
|
||||||
|
- [ ] **Parts 3-6**: Machine-generated drafts need complete rework
|
||||||
|
- [ ] **Parts 7-15**: Need creation this weekend
|
||||||
|
|
||||||
|
**Key Blockers**:
|
||||||
|
- Need smooth character movement animation (Pokemon-style)
|
||||||
|
- Grid needs walkable grass center, non-walkable tree edges
|
||||||
|
- Input queueing during animations not working properly
|
||||||
|
|
||||||
|
#### 2. Animation System Critical Issues 🚨
|
||||||
|
**BLOCKER FOR TUTORIAL PART 2**:
|
||||||
|
- [ ] **Input Queue System**: Holding arrow keys doesn't queue movements
|
||||||
|
- Animation must complete before next input accepted
|
||||||
|
- Need "press and hold" that queues ONE additional move
|
||||||
|
- Goal: Pokemon-style smooth continuous movement
|
||||||
|
- [ ] **Collision Reservation**: When entity starts moving, should block destination
|
||||||
|
- Prevents overlapping movements
|
||||||
|
- Already claimed tiles should reject incoming entities
|
||||||
|
- [x] **Segfault Fix**: Refactored from bare pointers to weak references ✅
|
||||||
|
|
||||||
|
#### 3. Grid Clicking BROKEN in Headless Mode 🚨
|
||||||
|
**MAJOR DISCOVERY**: All click events commented out!
|
||||||
|
- [ ] **#111** - Grid Click Events Broken in Headless: All click events commented out
|
||||||
|
- [ ] **Grid Click Coordinates**: Need tile coords, not just mouse coords
|
||||||
|
- [ ] **Nested Grid Support**: Clicks must work on grids within frames
|
||||||
|
- [ ] **No Error Reporting**: System claimed complete but isn't
|
||||||
|
|
||||||
|
#### 4. Python API Consistency Crisis
|
||||||
|
**Tutorial Writing Reveals Major Issues**:
|
||||||
|
- [ ] **#101/#110** - Inconsistent Constructors: Each class has different requirements
|
||||||
|
- [ ] **#109** - Vector Class Broken: No [0], [1] indexing like tuples
|
||||||
|
- [ ] **#112** - Object Splitting Bug: Python derived classes lose type in collections
|
||||||
|
- Shared pointer extracted, Python reference discarded
|
||||||
|
- Retrieved objects are base class only
|
||||||
|
- No way to cast back to derived type
|
||||||
|
- [ ] **Need Systematic Generation**: All bindings should be consistent
|
||||||
|
- [x] **UIGrid TCOD Integration** (8 hours) ✅ COMPLETED!
|
||||||
|
- ✅ Add TCODMap* to UIGrid constructor with proper lifecycle
|
||||||
|
- ✅ Implement complete Dijkstra pathfinding system
|
||||||
|
- ✅ Create mcrfpy.libtcod submodule with Python bindings
|
||||||
|
- ✅ Fix critical PyArg bug preventing Color object assignments
|
||||||
|
- ✅ Implement FOV with perspective rendering
|
||||||
|
- [ ] **#113** - Add batch operations for NumPy-style access (deferred)
|
||||||
|
- [ ] **#114** - Create CellView for ergonomic .at((x,y)) access (deferred)
|
||||||
|
- [x] **UIEntity Pathfinding** (4 hours) ✅ COMPLETED!
|
||||||
|
- ✅ Implement Dijkstra maps for multiple targets in UIGrid
|
||||||
|
- ✅ Add path_to(target) method using A* to UIEntity
|
||||||
|
- ✅ Cache paths in UIEntity for performance
|
||||||
|
|
||||||
|
#### 3. Performance Critical Path
|
||||||
|
- [ ] **#115** - Implement SpatialHash for 10,000+ entities (2 hours)
|
||||||
|
- [ ] **#116** - Add dirty flag system to UIGrid (1 hour)
|
||||||
|
- [ ] **#113** - Batch update context managers (2 hours)
|
||||||
|
- [ ] **#117** - Memory pool for entities (2 hours)
|
||||||
|
|
||||||
|
#### 4. Bug Fixing Pipeline
|
||||||
|
- [ ] **#125** - Set up GitHub Issues automation
|
||||||
|
- [ ] Create test for each bug before fixing
|
||||||
|
- [ ] Track: Memory leaks, Segfaults, Python/C++ boundary errors
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🏗️ PROPOSED ARCHITECTURE IMPROVEMENTS (From July 12 Analysis)
|
||||||
|
|
||||||
|
### Object-Oriented Design Overhaul
|
||||||
|
1. **Scene System Revolution**:
|
||||||
|
- [ ] **#118** - Make Scene derive from Drawable (scenes are drawn!)
|
||||||
|
- [ ] Give scenes position and visibility properties
|
||||||
|
- [ ] Scene selection by visibility (auto-hide old scene)
|
||||||
|
- [ ] Replace transition system with animations
|
||||||
|
|
||||||
|
2. **Animation System Enhancements**:
|
||||||
|
- [ ] **#119** - Add proper completion callbacks (object + animation params)
|
||||||
|
- [ ] **#120** - Prevent property conflicts (exclusive locking)
|
||||||
|
- [ ] Currently using timer sync workarounds
|
||||||
|
|
||||||
|
3. **Timer System Improvements**:
|
||||||
|
- [ ] **#121** - Replace string-dictionary system with objects
|
||||||
|
- [ ] Add start(), stop(), pause() methods
|
||||||
|
- [ ] Implement proper one-shot mode
|
||||||
|
- [ ] Pass timer object to callbacks (not just ms)
|
||||||
|
|
||||||
|
4. **Parent-Child UI Relationships**:
|
||||||
|
- [ ] **#122** - Add parent field to UI drawables (like entities have)
|
||||||
|
- [ ] Implement append/remove/extend with auto-parent updates
|
||||||
|
- [ ] Auto-remove from old parent when adding to new
|
||||||
|
|
||||||
|
### Performance Optimizations Needed
|
||||||
|
- [ ] **Grid Rendering**: Consider texture caching vs real-time
|
||||||
|
- [ ] **#123** - Subgrid System: Split large grids into 256x256 chunks
|
||||||
|
- [ ] **#116** - Dirty Flagging: Propagate from base class up
|
||||||
|
- [ ] **#124** - Animation Features: Tile color animation, sprite cycling
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## ⚠️ CLAUDE CODE QUALITY CONCERNS (6-7 Weeks In)
|
||||||
|
|
||||||
|
### Issues Observed:
|
||||||
|
1. **Declining Quality**: High quantity but low quality results
|
||||||
|
2. **Not Following Requirements**: Ignoring specific implementation needs
|
||||||
|
3. **Bad Practices**:
|
||||||
|
- Creating parallel copies (animation_RAII.cpp, _fixed, _final versions)
|
||||||
|
- Should use Git, not file copies
|
||||||
|
- Claims functionality "done" when stubbed out
|
||||||
|
4. **File Management Problems**:
|
||||||
|
- Git operations reset timestamps
|
||||||
|
- Can't determine creation order of multiple versions
|
||||||
|
|
||||||
|
### Recommendations:
|
||||||
|
- Use Git for version control exclusively
|
||||||
|
- Fix things in place, not copies
|
||||||
|
- Acknowledge incomplete functionality
|
||||||
|
- Follow project's implementation style
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎯 STRATEGIC ARCHITECTURE VISION
|
||||||
|
|
||||||
|
### Three-Layer Grid Architecture (From Compass Research)
|
||||||
|
Following successful roguelike patterns (Caves of Qud, Cogmind, DCSS):
|
||||||
|
|
||||||
|
1. **Visual Layer** (UIGridPoint) - Sprites, colors, animations
|
||||||
|
2. **World State Layer** (TCODMap) - Walkability, transparency, physics
|
||||||
|
3. **Entity Perspective Layer** (UIGridPointState) - Per-entity FOV, knowledge
|
||||||
|
|
||||||
|
### Performance Architecture (Critical for 1000x1000 maps)
|
||||||
|
- **Spatial Hashing** for entity queries (not quadtrees!)
|
||||||
|
- **Batch Operations** with context managers (10-100x speedup)
|
||||||
|
- **Memory Pooling** for entities and components
|
||||||
|
- **Dirty Flag System** to avoid unnecessary updates
|
||||||
|
- **Zero-Copy NumPy Integration** via buffer protocol
|
||||||
|
|
||||||
|
### Key Insight from Research
|
||||||
|
"Minimizing Python/C++ boundary crossings matters more than individual function complexity"
|
||||||
|
- Batch everything possible
|
||||||
|
- Use context managers for logical operations
|
||||||
|
- Expose arrays, not individual cells
|
||||||
|
- Profile and optimize hot paths only
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Project Status: 🎉 ALPHA 0.1 RELEASE! 🎉
|
||||||
|
|
||||||
|
**Current State**: Documentation system complete, TCOD integration urgent
|
||||||
|
**Latest Update**: Tutorial Parts 0-6 complete with documentation (2025-07-11)
|
||||||
|
**Branch**: alpha_streamline_2
|
||||||
|
**Open Issues**: ~46 remaining + URGENT TCOD/Tutorial work
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 TCOD Integration Implementation Details
|
||||||
|
|
||||||
|
### Phase 1: Core UIGrid Integration (Day 1 Morning)
|
||||||
|
```cpp
|
||||||
|
// UIGrid.h additions
|
||||||
|
class UIGrid : public UIDrawable {
|
||||||
|
private:
|
||||||
|
TCODMap* world_state; // Add TCOD map
|
||||||
|
std::unordered_map<int, UIGridPointState*> entity_perspectives;
|
||||||
|
bool batch_mode = false;
|
||||||
|
std::vector<CellUpdate> pending_updates;
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 2: Python Bindings (Day 1 Afternoon)
|
||||||
|
```python
|
||||||
|
# New API surface
|
||||||
|
grid = mcrfpy.Grid(100, 100)
|
||||||
|
grid.compute_fov(player.x, player.y, radius=10) # Returns visible cells
|
||||||
|
grid.at((x, y)).walkable = False # Ergonomic access
|
||||||
|
with grid.batch_update(): # Context manager for performance
|
||||||
|
# All updates batched
|
||||||
|
```
|
||||||
|
|
||||||
|
### Phase 3: Entity Integration (Day 2 Morning)
|
||||||
|
```python
|
||||||
|
# UIEntity additions
|
||||||
|
entity.path_to(target_x, target_y) # A* pathfinding
|
||||||
|
entity.flee_from(threat) # Dijkstra map
|
||||||
|
entity.can_see(other_entity) # FOV check
|
||||||
|
```
|
||||||
|
|
||||||
|
### Critical Success Factors:
|
||||||
|
1. **Batch everything** - Never update single cells in loops
|
||||||
|
2. **Lazy evaluation** - Only compute FOV for entities that need it
|
||||||
|
3. **Sparse storage** - Don't store full grids per entity
|
||||||
|
4. **Profile early** - Find the 20% of code taking 80% of time
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## Recent Achievements
|
||||||
|
|
||||||
|
### 2025-07-12: Animation System RAII Overhaul - Critical Segfault Fix! 🛡️
|
||||||
|
**Fixed two major crashes in AnimationManager**
|
||||||
|
- ✅ Race condition when creating animations in timer callbacks
|
||||||
|
- ✅ Exit crash when animations outlive their targets
|
||||||
|
- ✅ Implemented weak_ptr tracking for automatic cleanup
|
||||||
|
- ✅ Added complete() and hasValidTarget() methods
|
||||||
|
- ✅ No more use-after-free bugs - proper RAII design
|
||||||
|
- ✅ Extensively tested with stress tests and production demos
|
||||||
|
|
||||||
|
### 2025-07-10: Complete FOV, A* Pathfinding & GUI Text Widgets! 👁️🗺️⌨️
|
||||||
|
**Engine Feature Sprint - Major Capabilities Added**
|
||||||
|
- ✅ Complete FOV (Field of View) system with perspective rendering
|
||||||
|
- UIGrid.perspective property controls which entity's view to render
|
||||||
|
- Three-layer overlay system: unexplored (black), explored (dark), visible (normal)
|
||||||
|
- Per-entity visibility state tracking with UIGridPointState
|
||||||
|
- Perfect knowledge updates - only explored areas persist
|
||||||
|
- ✅ A* Pathfinding implementation
|
||||||
|
- Entity.path_to(x, y) method for direct pathfinding
|
||||||
|
- UIGrid compute_astar() and get_astar_path() methods
|
||||||
|
- Path caching in entities for performance
|
||||||
|
- Complete test suite comparing A* vs Dijkstra performance
|
||||||
|
- ✅ GUI Text Input Widget System
|
||||||
|
- Full-featured TextInputWidget class with cursor, selection, scrolling
|
||||||
|
- Improved widget with proper text rendering and multi-line support
|
||||||
|
- Example showcase demonstrating multiple input fields
|
||||||
|
- Foundation for in-game consoles, chat systems, and text entry
|
||||||
|
- ✅ Sizzle Reel Demos
|
||||||
|
- path_vision_sizzle_reel.py combines pathfinding with FOV
|
||||||
|
- Interactive visibility demos showing real-time FOV updates
|
||||||
|
- Performance demonstrations with multiple entities
|
||||||
|
|
||||||
|
### 2025-07-09: Dijkstra Pathfinding & Critical Bug Fix! 🗺️
|
||||||
|
**TCOD Integration Sprint - Major Progress**
|
||||||
|
- ✅ Complete Dijkstra pathfinding implementation in UIGrid
|
||||||
|
- compute_dijkstra(), get_dijkstra_distance(), get_dijkstra_path() methods
|
||||||
|
- Full TCODMap and TCODDijkstra integration with proper memory management
|
||||||
|
- Comprehensive test suite with both headless and interactive demos
|
||||||
|
- ✅ **CRITICAL FIX**: PyArg bug in UIGridPoint color setter
|
||||||
|
- Now supports both mcrfpy.Color objects and (r,g,b,a) tuples
|
||||||
|
- Eliminated mysterious "SystemError: new style getargs format" crashes
|
||||||
|
- Proper error handling and exception propagation
|
||||||
|
- ✅ mcrfpy.libtcod submodule with Python bindings
|
||||||
|
- dijkstra_compute(), dijkstra_get_distance(), dijkstra_get_path()
|
||||||
|
- line() function for corridor generation
|
||||||
|
- Foundation ready for FOV implementation
|
||||||
|
- ✅ Test consolidation: 6 broken demos → 2 clean, working versions
|
||||||
|
|
||||||
|
### 2025-07-08: PyArgHelpers Infrastructure Complete! 🔧
|
||||||
|
**Standardized Python API Argument Parsing**
|
||||||
|
- Unified position handling: (x, y) tuples or separate x, y args
|
||||||
|
- Consistent size parsing: (w, h) tuples or width, height args
|
||||||
|
- Grid-specific helpers for tile-based positioning
|
||||||
|
- Proper conflict detection between positional and keyword args
|
||||||
|
- All UI components migrated: Frame, Caption, Sprite, Grid, Entity
|
||||||
|
- Improved error messages: "Value must be a number (int or float)"
|
||||||
|
- Foundation for Phase 7 documentation efforts
|
||||||
|
|
||||||
|
### 2025-07-05: ALPHA 0.1 ACHIEVED! 🎊🍾
|
||||||
|
**All Alpha Blockers Resolved!**
|
||||||
|
- Z-order rendering with performance optimization (Issue #63)
|
||||||
|
- Python Sequence Protocol for collections (Issue #69)
|
||||||
|
- Comprehensive Animation System (Issue #59)
|
||||||
|
- Moved RenderTexture to Beta (not needed for Alpha)
|
||||||
|
- **McRogueFace is ready for Alpha release!**
|
||||||
|
|
||||||
|
### 2025-07-05: Z-order Rendering Complete! 🎉
|
||||||
|
**Issue #63 Resolved**: Consistent z-order rendering with performance optimization
|
||||||
|
- Dirty flag pattern prevents unnecessary per-frame sorting
|
||||||
|
- Lazy sorting for both Scene elements and Frame children
|
||||||
|
- Frame children now respect z_index (fixed inconsistency)
|
||||||
|
- Automatic dirty marking on z_index changes and collection modifications
|
||||||
|
- Performance: O(1) check for static scenes vs O(n log n) every frame
|
||||||
|
|
||||||
|
### 2025-07-05: Python Sequence Protocol Complete! 🎉
|
||||||
|
**Issue #69 Resolved**: Full sequence protocol implementation for collections
|
||||||
|
- Complete __setitem__, __delitem__, __contains__ support
|
||||||
|
- Slice operations with extended slice support (step != 1)
|
||||||
|
- Concatenation (+) and in-place concatenation (+=) with validation
|
||||||
|
- Negative indexing throughout, index() and count() methods
|
||||||
|
- Type safety: UICollection (Frame/Caption/Sprite/Grid), EntityCollection (Entity only)
|
||||||
|
- Default value support: None for texture/font parameters uses engine defaults
|
||||||
|
|
||||||
|
### 2025-07-05: Animation System Complete! 🎉
|
||||||
|
**Issue #59 Resolved**: Comprehensive animation system with 30+ easing functions
|
||||||
|
- Property-based animations for all UI classes (Frame, Caption, Sprite, Grid, Entity)
|
||||||
|
- Individual color component animation (r/g/b/a)
|
||||||
|
- Sprite sequence animation and text typewriter effects
|
||||||
|
- Pure C++ execution without Python callbacks
|
||||||
|
- Delta animation support for relative values
|
||||||
|
|
||||||
|
### 2025-01-03: Major Stability Update
|
||||||
|
**Major Cleanup**: Removed deprecated registerPyAction system (-180 lines)
|
||||||
|
**Bug Fixes**: 12 critical issues including Grid segfault, Issue #78 (middle click), Entity setters
|
||||||
|
**New Features**: Entity.index() (#73), EntityCollection.extend() (#27), Sprite validation (#33)
|
||||||
|
**Test Coverage**: Comprehensive test suite with timer callback pattern established
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔧 CURRENT WORK: Alpha Streamline 2 - Major Architecture Improvements
|
||||||
|
|
||||||
|
### Recent Completions:
|
||||||
|
- ✅ **Phase 1-4 Complete** - Foundation, API Polish, Entity Lifecycle, Visibility/Performance
|
||||||
|
- ✅ **Phase 5 Complete** - Window/Scene Architecture fully implemented!
|
||||||
|
- Window singleton with properties (#34)
|
||||||
|
- OOP Scene support with lifecycle methods (#61)
|
||||||
|
- Window resize events (#1)
|
||||||
|
- Scene transitions with animations (#105)
|
||||||
|
- ✅ **Phase 6 Complete** - Rendering Revolution achieved!
|
||||||
|
- Grid background colors (#50) ✅
|
||||||
|
- RenderTexture overhaul (#6) ✅
|
||||||
|
- UIFrame clipping support ✅
|
||||||
|
- Viewport-based rendering (#8) ✅
|
||||||
|
|
||||||
|
### Active Development:
|
||||||
|
- **Branch**: alpha_streamline_2
|
||||||
|
- **Current Phase**: Phase 7 - Documentation & Distribution
|
||||||
|
- **Achievement**: PyArgHelpers infrastructure complete - standardized Python API
|
||||||
|
- **Strategic Vision**: See STRATEGIC_VISION.md for platform roadmap
|
||||||
|
- **Latest**: All UI components now use consistent argument parsing patterns!
|
||||||
|
|
||||||
|
### 🏗️ Architectural Dependencies Map
|
||||||
|
|
||||||
|
```
|
||||||
|
Foundation Layer:
|
||||||
|
├── #71 Base Class (_Drawable)
|
||||||
|
│ ├── #10 Visibility System (needs AABB from base)
|
||||||
|
│ ├── #87 visible property
|
||||||
|
│ └── #88 opacity property
|
||||||
|
│
|
||||||
|
├── #7 Safe Constructors (affects all classes)
|
||||||
|
│ └── Blocks any new class creation until resolved
|
||||||
|
│
|
||||||
|
└── #30 Entity/Grid Integration (lifecycle management)
|
||||||
|
└── Enables reliable entity management
|
||||||
|
|
||||||
|
Window/Scene Layer:
|
||||||
|
├── #34 Window Object
|
||||||
|
│ ├── #61 Scene Object (depends on Window)
|
||||||
|
│ ├── #14 SFML Exposure (helps implement Window)
|
||||||
|
│ └── Future: Multi-window support
|
||||||
|
|
||||||
|
Rendering Layer:
|
||||||
|
└── #6 RenderTexture Overhaul
|
||||||
|
├── Enables clipping
|
||||||
|
├── Off-screen rendering
|
||||||
|
└── Post-processing effects
|
||||||
|
```
|
||||||
|
|
||||||
|
## 🚀 Alpha Streamline 2 - Comprehensive Phase Plan
|
||||||
|
|
||||||
|
### Phase 1: Foundation Stabilization (1-2 weeks)
|
||||||
|
**Goal**: Safe, predictable base for all future work
|
||||||
|
```
|
||||||
|
1. #7 - Audit and fix unsafe constructors (CRITICAL - do first!)
|
||||||
|
- Find all manually implemented no-arg constructors
|
||||||
|
- Verify map compatibility requirements
|
||||||
|
- Make pointer-safe or remove
|
||||||
|
|
||||||
|
2. #71 - _Drawable base class implementation
|
||||||
|
- Common properties: x, y, w, h, visible, opacity
|
||||||
|
- Virtual methods: get_bounds(), render()
|
||||||
|
- Proper Python inheritance setup
|
||||||
|
|
||||||
|
3. #87 - visible property
|
||||||
|
- Add to base class
|
||||||
|
- Update all render methods to check
|
||||||
|
|
||||||
|
4. #88 - opacity property (depends on #87)
|
||||||
|
- 0.0-1.0 float range
|
||||||
|
- Apply in render methods
|
||||||
|
|
||||||
|
5. #89 - get_bounds() method
|
||||||
|
- Virtual method returning (x, y, w, h)
|
||||||
|
- Override in each UI class
|
||||||
|
|
||||||
|
6. #98 - move()/resize() convenience methods
|
||||||
|
- move(dx, dy) - relative movement
|
||||||
|
- resize(w, h) - absolute sizing
|
||||||
|
```
|
||||||
|
*Rationale*: Can't build on unsafe foundations. Base class enables all UI improvements.
|
||||||
|
|
||||||
|
### Phase 2: Constructor & API Polish (1 week)
|
||||||
|
**Goal**: Pythonic, intuitive API
|
||||||
|
```
|
||||||
|
1. #101 - Standardize (0,0) defaults for all positions
|
||||||
|
2. #38 - Frame children parameter: Frame(children=[...])
|
||||||
|
3. #42 - Click handler in __init__: Button(click=callback)
|
||||||
|
4. #90 - Grid size tuple: Grid(grid_size=(10, 10))
|
||||||
|
5. #19 - Sprite texture swapping: sprite.texture = new_texture
|
||||||
|
6. #52 - Grid skip out-of-bounds entities (performance)
|
||||||
|
```
|
||||||
|
*Rationale*: Quick wins that make the API more pleasant before bigger changes.
|
||||||
|
|
||||||
|
### Phase 3: Entity Lifecycle Management (1 week)
|
||||||
|
**Goal**: Bulletproof entity/grid relationships
|
||||||
|
```
|
||||||
|
1. #30 - Entity.die() and grid association
|
||||||
|
- Grid.entities.append(e) sets e.grid = self
|
||||||
|
- Grid.entities.remove(e) sets e.grid = None
|
||||||
|
- Entity.die() calls self.grid.remove(self)
|
||||||
|
- Entity can only be in 0 or 1 grid
|
||||||
|
|
||||||
|
2. #93 - Vector arithmetic methods
|
||||||
|
- add, subtract, multiply, divide
|
||||||
|
- distance, normalize, dot product
|
||||||
|
|
||||||
|
3. #94 - Color helper methods
|
||||||
|
- from_hex("#FF0000"), to_hex()
|
||||||
|
- lerp(other_color, t) for interpolation
|
||||||
|
|
||||||
|
4. #103 - Timer objects
|
||||||
|
timer = mcrfpy.Timer("my_timer", callback, 1000)
|
||||||
|
timer.pause()
|
||||||
|
timer.resume()
|
||||||
|
timer.cancel()
|
||||||
|
```
|
||||||
|
*Rationale*: Games need reliable entity management. Timer objects enable entity AI.
|
||||||
|
|
||||||
|
### Phase 4: Visibility & Performance (1-2 weeks)
|
||||||
|
**Goal**: Only render/process what's needed
|
||||||
|
```
|
||||||
|
1. #10 - [UNSCHEDULED] Full visibility system with AABB
|
||||||
|
- Postponed: UIDrawables can exist in multiple collections
|
||||||
|
- Cannot reliably determine screen position due to multiple render contexts
|
||||||
|
- Needs architectural solution for parent-child relationships
|
||||||
|
|
||||||
|
2. #52 - Grid culling (COMPLETED in Phase 2)
|
||||||
|
|
||||||
|
3. #39/40/41 - Name system for finding elements
|
||||||
|
- name="button1" property on all UIDrawables
|
||||||
|
- only_one=True for unique names
|
||||||
|
- scene.find("button1") returns element
|
||||||
|
- collection.find("enemy*") returns list
|
||||||
|
|
||||||
|
4. #104 - Basic profiling/metrics
|
||||||
|
- Frame time tracking
|
||||||
|
- Draw call counting
|
||||||
|
- Python vs C++ time split
|
||||||
|
```
|
||||||
|
*Rationale*: Performance is feature. Finding elements by name is huge QoL.
|
||||||
|
|
||||||
|
### Phase 5: Window/Scene Architecture ✅ COMPLETE! (2025-07-06)
|
||||||
|
**Goal**: Modern, flexible architecture
|
||||||
|
```
|
||||||
|
1. ✅ #34 - Window object (singleton first)
|
||||||
|
window = mcrfpy.Window.get()
|
||||||
|
window.resolution = (1920, 1080)
|
||||||
|
window.fullscreen = True
|
||||||
|
window.vsync = True
|
||||||
|
|
||||||
|
2. ✅ #1 - Window resize events
|
||||||
|
scene.on_resize(self, width, height) callback implemented
|
||||||
|
|
||||||
|
3. ✅ #61 - Scene object (OOP scenes)
|
||||||
|
class MenuScene(mcrfpy.Scene):
|
||||||
|
def on_keypress(self, key, state):
|
||||||
|
# handle input
|
||||||
|
def on_enter(self):
|
||||||
|
# setup UI
|
||||||
|
def on_exit(self):
|
||||||
|
# cleanup
|
||||||
|
def update(self, dt):
|
||||||
|
# frame update
|
||||||
|
|
||||||
|
4. ✅ #14 - SFML exposure research
|
||||||
|
- Completed comprehensive analysis
|
||||||
|
- Recommendation: Direct integration as mcrfpy.sfml
|
||||||
|
- SFML 3.0 migration deferred to late 2025
|
||||||
|
|
||||||
|
5. ✅ #105 - Scene transitions
|
||||||
|
mcrfpy.setScene("menu", "fade", 1.0)
|
||||||
|
# Supports: fade, slide_left, slide_right, slide_up, slide_down
|
||||||
|
```
|
||||||
|
*Result*: Entire window/scene system modernized with OOP design!
|
||||||
|
|
||||||
|
### Phase 6: Rendering Revolution (3-4 weeks) ✅ COMPLETE!
|
||||||
|
**Goal**: Professional rendering capabilities
|
||||||
|
```
|
||||||
|
1. ✅ #50 - Grid background colors [COMPLETED]
|
||||||
|
grid.background_color = mcrfpy.Color(50, 50, 50)
|
||||||
|
- Added background_color property with animation support
|
||||||
|
- Default dark gray background (8, 8, 8, 255)
|
||||||
|
|
||||||
|
2. ✅ #6 - RenderTexture overhaul [COMPLETED]
|
||||||
|
✅ Base infrastructure in UIDrawable
|
||||||
|
✅ UIFrame clip_children property
|
||||||
|
✅ Dirty flag optimization system
|
||||||
|
✅ Nested clipping support
|
||||||
|
✅ UIGrid already has appropriate RenderTexture implementation
|
||||||
|
❌ UICaption/UISprite clipping not needed (no children)
|
||||||
|
|
||||||
|
3. ✅ #8 - Viewport-based rendering [COMPLETED]
|
||||||
|
- Fixed game resolution (window.game_resolution)
|
||||||
|
- Three scaling modes: "center", "stretch", "fit"
|
||||||
|
- Window to game coordinate transformation
|
||||||
|
- Mouse input properly scaled with windowToGameCoords()
|
||||||
|
- Python API fully integrated
|
||||||
|
- Tests: test_viewport_simple.py, test_viewport_visual.py, test_viewport_scaling.py
|
||||||
|
|
||||||
|
4. #106 - Shader support [DEFERRED TO POST-PHASE 7]
|
||||||
|
sprite.shader = mcrfpy.Shader.load("glow.frag")
|
||||||
|
frame.shader_params = {"intensity": 0.5}
|
||||||
|
|
||||||
|
5. #107 - Particle system [DEFERRED TO POST-PHASE 7]
|
||||||
|
emitter = mcrfpy.ParticleEmitter()
|
||||||
|
emitter.texture = spark_texture
|
||||||
|
emitter.emission_rate = 100
|
||||||
|
emitter.lifetime = (0.5, 2.0)
|
||||||
|
```
|
||||||
|
|
||||||
|
**Phase 6 Achievement Summary**:
|
||||||
|
- Grid backgrounds (#50) ✅ - Customizable background colors with animation
|
||||||
|
- RenderTexture overhaul (#6) ✅ - UIFrame clipping with opt-in architecture
|
||||||
|
- Viewport rendering (#8) ✅ - Three scaling modes with coordinate transformation
|
||||||
|
- UIGrid already had optimal RenderTexture implementation for its use case
|
||||||
|
- UICaption/UISprite clipping unnecessary (no children to clip)
|
||||||
|
- Performance optimized with dirty flag system
|
||||||
|
- Backward compatibility preserved throughout
|
||||||
|
- Effects/Shader/Particle systems deferred for focused delivery
|
||||||
|
|
||||||
|
*Rationale*: This unlocks professional visual effects but is complex.
|
||||||
|
|
||||||
|
### Phase 7: Documentation & Distribution (1-2 weeks)
|
||||||
|
**Goal**: Ready for the world
|
||||||
|
```
|
||||||
|
1. ✅ #85 - Replace all "docstring" placeholders [COMPLETED 2025-07-08]
|
||||||
|
2. ✅ #86 - Add parameter documentation [COMPLETED 2025-07-08]
|
||||||
|
3. ✅ #108 - Generate .pyi type stubs for IDE support [COMPLETED 2025-07-08]
|
||||||
|
4. ❌ #70 - PyPI wheel preparation [CANCELLED - Architectural mismatch]
|
||||||
|
5. API reference generator tool
|
||||||
|
```
|
||||||
|
|
||||||
|
## 📋 Critical Path & Parallel Tracks
|
||||||
|
|
||||||
|
### 🔴 **Critical Path** (Must do in order)
|
||||||
|
**Safe Constructors (#7)** → **Base Class (#71)** → **Visibility (#10)** → **Window (#34)** → **Scene (#61)**
|
||||||
|
|
||||||
|
### 🟡 **Parallel Tracks** (Can be done alongside critical path)
|
||||||
|
|
||||||
|
**Track A: Entity Systems**
|
||||||
|
- Entity/Grid integration (#30)
|
||||||
|
- Timer objects (#103)
|
||||||
|
- Vector/Color helpers (#93, #94)
|
||||||
|
|
||||||
|
**Track B: API Polish**
|
||||||
|
- Constructor improvements (#101, #38, #42, #90)
|
||||||
|
- Sprite texture swap (#19)
|
||||||
|
- Name/search system (#39/40/41)
|
||||||
|
|
||||||
|
**Track C: Performance**
|
||||||
|
- Grid culling (#52)
|
||||||
|
- Visibility culling (part of #10)
|
||||||
|
- Profiling tools (#104)
|
||||||
|
|
||||||
|
### 💎 **Quick Wins to Sprinkle Throughout**
|
||||||
|
1. Color helpers (#94) - 1 hour
|
||||||
|
2. Vector methods (#93) - 1 hour
|
||||||
|
3. Grid backgrounds (#50) - 30 minutes
|
||||||
|
4. Default positions (#101) - 30 minutes
|
||||||
|
|
||||||
|
### 🎯 **Recommended Execution Order**
|
||||||
|
|
||||||
|
**Week 1-2**: Foundation (Critical constructors + base class)
|
||||||
|
**Week 3**: Entity lifecycle + API polish
|
||||||
|
**Week 4**: Visibility system + performance
|
||||||
|
**Week 5-6**: Window/Scene architecture
|
||||||
|
**Week 7-9**: Rendering revolution (or defer to gamma)
|
||||||
|
**Week 10**: Documentation + release prep
|
||||||
|
|
||||||
|
### 🆕 **New Issues to Create/Track**
|
||||||
|
|
||||||
|
1. [x] **Timer Objects** - Pythonic timer management (#103) - *Completed Phase 3*
|
||||||
|
2. [ ] **Event System Enhancement** - Mouse enter/leave, drag, right-click
|
||||||
|
3. [ ] **Resource Manager** - Centralized asset loading
|
||||||
|
4. [ ] **Serialization System** - Save/load game state
|
||||||
|
5. [x] **Scene Transitions** - Fade, slide, custom effects (#105) - *Completed Phase 5*
|
||||||
|
6. [x] **Profiling Tools** - Performance metrics (#104) - *Completed Phase 4*
|
||||||
|
7. [ ] **Particle System** - Visual effects framework (#107)
|
||||||
|
8. [ ] **Shader Support** - Custom rendering effects (#106)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 Phase 6 Implementation Strategy
|
||||||
|
|
||||||
|
### RenderTexture Overhaul (#6) - Technical Approach
|
||||||
|
|
||||||
|
**Current State**:
|
||||||
|
- UIGrid already uses RenderTexture for entity rendering
|
||||||
|
- Scene transitions use RenderTextures for smooth animations
|
||||||
|
- Direct rendering to window for Frame, Caption, Sprite
|
||||||
|
|
||||||
|
**Implementation Plan**:
|
||||||
|
1. **Base Infrastructure**:
|
||||||
|
- Add `sf::RenderTexture* target` to UIDrawable base
|
||||||
|
- Modify `render()` to check if target exists
|
||||||
|
- If target: render to texture, then draw texture to parent
|
||||||
|
- If no target: render directly (backward compatible)
|
||||||
|
|
||||||
|
2. **Clipping Support**:
|
||||||
|
- Frame enforces bounds on children via RenderTexture
|
||||||
|
- Children outside bounds are automatically clipped
|
||||||
|
- Nested frames create render texture hierarchy
|
||||||
|
|
||||||
|
3. **Performance Optimization**:
|
||||||
|
- Lazy RenderTexture creation (only when needed)
|
||||||
|
- Dirty flag system (only re-render when changed)
|
||||||
|
- Texture pooling for commonly used sizes
|
||||||
|
|
||||||
|
4. **Integration Points**:
|
||||||
|
- Scene transitions already working with RenderTextures
|
||||||
|
- UIGrid can be reference implementation
|
||||||
|
- Test with deeply nested UI structures
|
||||||
|
|
||||||
|
**Quick Wins Before Core Work**:
|
||||||
|
1. **Grid Background (#50)** - 30 min implementation
|
||||||
|
- Add `background_color` and `background_texture` properties
|
||||||
|
- Render before entities in UIGrid::render()
|
||||||
|
- Good warm-up before tackling RenderTexture
|
||||||
|
|
||||||
|
2. **Research Tasks**:
|
||||||
|
- Study UIGrid's current RenderTexture usage
|
||||||
|
- Profile scene transition performance
|
||||||
|
- Identify potential texture size limits
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 NEXT PHASE: Beta Features & Polish
|
||||||
|
|
||||||
|
### Alpha Complete! Moving to Beta Priorities:
|
||||||
|
1. ~~**#69** - Python Sequence Protocol for collections~~ - *Completed! (2025-07-05)*
|
||||||
|
2. ~~**#63** - Z-order rendering for UIDrawables~~ - *Completed! (2025-07-05)*
|
||||||
|
3. ~~**#59** - Animation system~~ - *Completed! (2025-07-05)*
|
||||||
|
4. **#6** - RenderTexture concept - *Extensive Overhaul*
|
||||||
|
5. ~~**#47** - New README.md for Alpha release~~ - *Completed*
|
||||||
|
- [x] **#78** - Middle Mouse Click sends "C" keyboard event - *Fixed*
|
||||||
|
- [x] **#77** - Fix error message copy/paste bug - *Fixed*
|
||||||
|
- [x] **#74** - Add missing `Grid.grid_y` property - *Fixed*
|
||||||
|
- [ ] **#37** - Fix Windows build module import from "scripts" directory - *Isolated Fix*
|
||||||
|
Issue #37 is **on hold** until we have a Windows build environment available. I actually suspect this is already fixed by the updates to the makefile, anyway.
|
||||||
|
- [x] **Entity Property Setters** - Fix "new style getargs format" error - *Fixed*
|
||||||
|
- [x] **Sprite Texture Setter** - Fix "error return without exception set" - *Fixed*
|
||||||
|
- [x] **keypressScene() Validation** - Add proper error handling - *Fixed*
|
||||||
|
|
||||||
|
### 🔄 Complete Iterator System
|
||||||
|
**Status**: Core iterators complete (#72 closed), Grid point iterators still pending
|
||||||
|
|
||||||
|
- [ ] **Grid Point Iterator Implementation** - Complete the remaining grid iteration work
|
||||||
|
- [x] **#73** - Add `entity.index()` method for collection removal - *Fixed*
|
||||||
|
- [x] **#69** ⚠️ **Alpha Blocker** - Refactor all collections to use Python Sequence Protocol - *Completed! (2025-07-05)*
|
||||||
|
|
||||||
|
**Dependencies**: Grid point iterators → #73 entity.index() → #69 Sequence Protocol overhaul
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🗂 ISSUE TRIAGE BY SYSTEM (78 Total Issues)
|
||||||
|
|
||||||
|
### 🎮 Core Engine Systems
|
||||||
|
|
||||||
|
#### Iterator/Collection System (2 issues)
|
||||||
|
- [x] **#73** - Entity index() method for removal - *Fixed*
|
||||||
|
- [x] **#69** ⚠️ **Alpha Blocker** - Sequence Protocol refactor - *Completed! (2025-07-05)*
|
||||||
|
|
||||||
|
#### Python/C++ Integration (7 issues)
|
||||||
|
- [x] **#76** - UIEntity derived type preservation in collections - *Multiple Integrations*
|
||||||
|
- [ ] **#71** - Drawable base class hierarchy - *Extensive Overhaul*
|
||||||
|
- [ ] **#70** - PyPI wheel distribution - *Extensive Overhaul*
|
||||||
|
- [~] **#32** - Executable behave like `python` command - *Extensive Overhaul* *(90% Complete: -h, -V, -c, -m, -i, script execution, sys.argv, --exec all implemented. Only stdin (-) support missing)*
|
||||||
|
- [ ] **#35** - TCOD as built-in module - *Extensive Overhaul*
|
||||||
|
- [~] **#14** - Expose SFML as built-in module - *Research Complete, Implementation Pending*
|
||||||
|
- [ ] **#46** - Subinterpreter threading tests - *Multiple Integrations*
|
||||||
|
|
||||||
|
#### UI/Rendering System (12 issues)
|
||||||
|
- [x] **#63** ⚠️ **Alpha Blocker** - Z-order for UIDrawables - *Multiple Integrations*
|
||||||
|
- [x] **#59** ⚠️ **Alpha Blocker** - Animation system - *Completed! (2025-07-05)*
|
||||||
|
- [ ] **#6** ⚠️ **Alpha Blocker** - RenderTexture for all UIDrawables - *Extensive Overhaul*
|
||||||
|
- [ ] **#10** - UIDrawable visibility/AABB system - *Extensive Overhaul*
|
||||||
|
- [ ] **#8** - UIGrid RenderTexture viewport sizing - *Multiple Integrations*
|
||||||
|
- [x] **#9** - UIGrid RenderTexture resize handling - *Multiple Integrations*
|
||||||
|
- [ ] **#52** - UIGrid skip out-of-bounds entities - *Isolated Fix*
|
||||||
|
- [ ] **#50** - UIGrid background color field - *Isolated Fix*
|
||||||
|
- [ ] **#19** - Sprite get/set texture methods - *Multiple Integrations*
|
||||||
|
- [ ] **#17** - Move UISprite position into sf::Sprite - *Isolated Fix*
|
||||||
|
- [x] **#33** - Sprite index validation against texture range - *Fixed*
|
||||||
|
|
||||||
|
#### Grid/Entity System (6 issues)
|
||||||
|
- [ ] **#30** - Entity/Grid association management (.die() method) - *Extensive Overhaul*
|
||||||
|
- [ ] **#16** - Grid strict mode for entity knowledge/visibility - *Extensive Overhaul*
|
||||||
|
- [ ] **#67** - Grid stitching for infinite worlds - *Extensive Overhaul*
|
||||||
|
- [ ] **#15** - UIGridPointState cleanup and standardization - *Multiple Integrations*
|
||||||
|
- [ ] **#20** - UIGrid get_grid_size standardization - *Multiple Integrations*
|
||||||
|
- [x] **#12** - GridPoint/GridPointState forbid direct init - *Isolated Fix*
|
||||||
|
|
||||||
|
#### Scene/Window Management (5 issues)
|
||||||
|
- [x] **#61** - Scene object encapsulating key callbacks - *Completed Phase 5*
|
||||||
|
- [x] **#34** - Window object for resolution/scaling - *Completed Phase 5*
|
||||||
|
- [ ] **#62** - Multiple windows support - *Extensive Overhaul*
|
||||||
|
- [ ] **#49** - Window resolution & viewport controls - *Multiple Integrations*
|
||||||
|
- [x] **#1** - Scene resize event handling - *Completed Phase 5*
|
||||||
|
|
||||||
|
### 🔧 Quality of Life Features
|
||||||
|
|
||||||
|
#### UI Enhancement Features (8 issues)
|
||||||
|
- [ ] **#39** - Name field on UIDrawables - *Multiple Integrations*
|
||||||
|
- [ ] **#40** - `only_one` arg for unique naming - *Multiple Integrations*
|
||||||
|
- [ ] **#41** - `.find(name)` method for collections - *Multiple Integrations*
|
||||||
|
- [ ] **#38** - `children` arg for Frame initialization - *Isolated Fix*
|
||||||
|
- [ ] **#42** - Click callback arg for UIDrawable init - *Isolated Fix*
|
||||||
|
- [x] **#27** - UIEntityCollection.extend() method - *Fixed*
|
||||||
|
- [ ] **#28** - UICollectionIter for scene ui iteration - *Isolated Fix*
|
||||||
|
- [ ] **#26** - UIEntityCollectionIter implementation - *Isolated Fix*
|
||||||
|
|
||||||
|
### 🧹 Refactoring & Cleanup
|
||||||
|
|
||||||
|
#### Code Cleanup (7 issues)
|
||||||
|
- [x] **#3** ⚠️ **Alpha Blocker** - Remove `McRFPy_API::player_input` - *Completed*
|
||||||
|
- [x] **#2** ⚠️ **Alpha Blocker** - Review `registerPyAction` necessity - *Completed*
|
||||||
|
- [ ] **#7** - Remove unsafe no-argument constructors - *Multiple Integrations*
|
||||||
|
- [ ] **#21** - PyUIGrid dealloc cleanup - *Isolated Fix*
|
||||||
|
- [ ] **#75** - REPL thread separation from SFML window - *Multiple Integrations*
|
||||||
|
|
||||||
|
### 📚 Demo & Documentation
|
||||||
|
|
||||||
|
#### Documentation (2 issues)
|
||||||
|
- [x] **#47** ⚠️ **Alpha Blocker** - Alpha release README.md - *Isolated Fix*
|
||||||
|
- [ ] **#48** - Dependency compilation documentation - *Isolated Fix*
|
||||||
|
|
||||||
|
#### Demo Projects (6 issues)
|
||||||
|
- [ ] **#54** - Jupyter notebook integration demo - *Multiple Integrations*
|
||||||
|
- [ ] **#55** - Hunt the Wumpus AI demo - *Multiple Integrations*
|
||||||
|
- [ ] **#53** - Web interface input demo - *Multiple Integrations* *(New automation API could help)*
|
||||||
|
- [ ] **#45** - Accessibility mode demos - *Multiple Integrations* *(New automation API could help test)*
|
||||||
|
- [ ] **#36** - Dear ImGui integration tests - *Extensive Overhaul*
|
||||||
|
- [ ] **#65** - Python Explorer scene (replaces uitest) - *Extensive Overhaul*
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🎮 STRATEGIC DIRECTION
|
||||||
|
|
||||||
|
### Engine Philosophy Maintained
|
||||||
|
- **C++ First**: Performance-critical code stays in C++
|
||||||
|
- **Python Close Behind**: Rich scripting without frame-rate impact
|
||||||
|
- **Game-Ready**: Each improvement should benefit actual game development
|
||||||
|
|
||||||
|
### Architecture Goals
|
||||||
|
1. **Clean Inheritance**: Drawable → UI components, proper type preservation
|
||||||
|
2. **Collection Consistency**: Uniform iteration, indexing, and search patterns
|
||||||
|
3. **Resource Management**: RAII everywhere, proper lifecycle handling
|
||||||
|
4. **Multi-Platform**: Windows/Linux feature parity maintained
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📚 REFERENCES & CONTEXT
|
||||||
|
|
||||||
|
**Issue Dependencies** (Key Chains):
|
||||||
|
- Iterator System: Grid points → #73 → #69 (Alpha Blocker)
|
||||||
|
- UI Hierarchy: #71 → #63 (Alpha Blocker)
|
||||||
|
- Rendering: #6 (Alpha Blocker) → #8, #9 → #10
|
||||||
|
- Entity System: #30 → #16 → #67
|
||||||
|
- Window Management: #34 → #49, #61 → #62
|
||||||
|
|
||||||
|
**Commit References**:
|
||||||
|
- 167636c: Iterator improvements (UICollection/UIEntityCollection complete)
|
||||||
|
- Recent work: 7DRL 2025 completion, RPATH updates, console improvements
|
||||||
|
|
||||||
|
**Architecture Files**:
|
||||||
|
- Iterator patterns: src/UICollection.cpp, src/UIGrid.cpp
|
||||||
|
- Python integration: src/McRFPy_API.cpp, src/PyObjectUtils.h
|
||||||
|
- Game implementation: src/scripts/ (Crypt of Sokoban complete game)
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🔮 FUTURE VISION: Pure Python Extension Architecture
|
||||||
|
|
||||||
|
### Concept: McRogueFace as a Traditional Python Package
|
||||||
|
**Status**: Unscheduled - Long-term vision
|
||||||
|
**Complexity**: Major architectural overhaul
|
||||||
|
|
||||||
|
Instead of being a C++ application that embeds Python, McRogueFace could be redesigned as a pure Python extension module that can be installed via `pip install mcrogueface`.
|
||||||
|
|
||||||
|
### Technical Approach
|
||||||
|
1. **Separate Core Engine from Python Embedding**
|
||||||
|
- Extract SFML rendering, audio, and input into C++ extension modules
|
||||||
|
- Remove embedded CPython interpreter
|
||||||
|
- Use Python's C API to expose functionality
|
||||||
|
|
||||||
|
2. **Module Structure**
|
||||||
|
```
|
||||||
|
mcrfpy/
|
||||||
|
├── __init__.py # Pure Python coordinator
|
||||||
|
├── _core.so # C++ rendering/game loop extension
|
||||||
|
├── _sfml.so # SFML bindings
|
||||||
|
├── _audio.so # Audio system bindings
|
||||||
|
└── engine.py # Python game engine logic
|
||||||
|
```
|
||||||
|
|
||||||
|
3. **Inverted Control Flow**
|
||||||
|
- Python drives the main loop instead of C++
|
||||||
|
- C++ extensions handle performance-critical operations
|
||||||
|
- Python manages game logic, scenes, and entity systems
|
||||||
|
|
||||||
|
### Benefits
|
||||||
|
- **Standard Python Packaging**: `pip install mcrogueface`
|
||||||
|
- **Virtual Environment Support**: Works with venv, conda, poetry
|
||||||
|
- **Better IDE Integration**: Standard Python development workflow
|
||||||
|
- **Easier Testing**: Use pytest, standard Python testing tools
|
||||||
|
- **Cross-Python Compatibility**: Support multiple Python versions
|
||||||
|
- **Modular Architecture**: Users can import only what they need
|
||||||
|
|
||||||
|
### Challenges
|
||||||
|
- **Major Refactoring**: Complete restructure of codebase
|
||||||
|
- **Performance Considerations**: Python-driven main loop overhead
|
||||||
|
- **Build Complexity**: Multiple extension modules to compile
|
||||||
|
- **Platform Support**: Need wheels for many platform/Python combinations
|
||||||
|
- **API Stability**: Would need careful design to maintain compatibility
|
||||||
|
|
||||||
|
### Implementation Phases (If Pursued)
|
||||||
|
1. **Proof of Concept**: Simple SFML binding as Python extension
|
||||||
|
2. **Core Extraction**: Separate rendering from Python embedding
|
||||||
|
3. **Module Design**: Define clean API boundaries
|
||||||
|
4. **Incremental Migration**: Move systems one at a time
|
||||||
|
5. **Compatibility Layer**: Support existing games during transition
|
||||||
|
|
||||||
|
### Example Usage (Future Vision)
|
||||||
|
```python
|
||||||
|
import mcrfpy
|
||||||
|
from mcrfpy import Scene, Frame, Sprite, Grid
|
||||||
|
|
||||||
|
# Create game directly in Python
|
||||||
|
game = mcrfpy.Game(width=1024, height=768)
|
||||||
|
|
||||||
|
# Define scenes using Python classes
|
||||||
|
class MainMenu(Scene):
|
||||||
|
def on_enter(self):
|
||||||
|
self.ui.append(Frame(100, 100, 200, 50))
|
||||||
|
self.ui.append(Sprite("logo.png", x=400, y=100))
|
||||||
|
|
||||||
|
def on_keypress(self, key, pressed):
|
||||||
|
if key == "ENTER" and pressed:
|
||||||
|
self.game.set_scene("game")
|
||||||
|
|
||||||
|
# Run the game
|
||||||
|
game.add_scene("menu", MainMenu())
|
||||||
|
game.run()
|
||||||
|
```
|
||||||
|
|
||||||
|
This architecture would make McRogueFace a first-class Python citizen, following standard Python packaging conventions while maintaining high performance through C++ extensions.
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 🚀 IMMEDIATE NEXT STEPS (Priority Order)
|
||||||
|
|
||||||
|
### TODAY (July 12) - CRITICAL PATH:
|
||||||
|
1. **FIX ANIMATION BLOCKERS** for Tutorial Part 2:
|
||||||
|
- Implement input queueing during animations
|
||||||
|
- Add destination square reservation
|
||||||
|
- Test Pokemon-style continuous movement
|
||||||
|
2. **FIX GRID CLICKING** (discovered broken in headless):
|
||||||
|
- Uncomment and implement click events
|
||||||
|
- Add tile coordinate conversion
|
||||||
|
- Enable nested grid support
|
||||||
|
3. **CREATE TUTORIAL ANNOUNCEMENT** if blockers fixed
|
||||||
|
|
||||||
|
### Weekend (July 13-14) - Tutorial Sprint:
|
||||||
|
1. **Regenerate Parts 3-6** (machine drafts are broken)
|
||||||
|
2. **Create Parts 7-10**: Interface, Items, Targeting, Save/Load
|
||||||
|
3. **Create Parts 11-15**: Dungeon levels, difficulty, equipment
|
||||||
|
4. **Post more frequently during event** (narrator emphasis)
|
||||||
|
|
||||||
|
### Architecture Decision Log:
|
||||||
|
- **DECIDED**: Use three-layer architecture (visual/world/perspective)
|
||||||
|
- **DECIDED**: Spatial hashing over quadtrees for entities
|
||||||
|
- **DECIDED**: Batch operations are mandatory, not optional
|
||||||
|
- **DECIDED**: TCOD integration as mcrfpy.libtcod submodule
|
||||||
|
- **DECIDED**: Tutorial must showcase McRogueFace strengths, not mimic TCOD
|
||||||
|
|
||||||
|
### Risk Mitigation:
|
||||||
|
- **If TCOD integration delays**: Use pure Python FOV for tutorial
|
||||||
|
- **If performance issues**: Focus on <100x100 maps for demos
|
||||||
|
- **If tutorial incomplete**: Ship with 4 solid parts + roadmap
|
||||||
|
- **If bugs block progress**: Document as "known issues" and continue
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
## 📋 COMPREHENSIVE ISSUES FROM TRANSCRIPT ANALYSIS
|
||||||
|
|
||||||
|
### Animation System (6 issues)
|
||||||
|
1. **Input Queue During Animation**: Queue one additional move during animation
|
||||||
|
2. **Destination Square Reservation**: Block target when movement begins
|
||||||
|
3. **Pokemon-Style Movement**: Smooth continuous movement with input handling
|
||||||
|
4. **#119** - Animation Callbacks: Add completion callbacks with parameters
|
||||||
|
5. **#120** - Property Conflict Prevention: Prevent multiple animations on same property
|
||||||
|
6. **Remove Bare Pointers**: Complete refactoring to weak references ✅
|
||||||
|
|
||||||
|
### Grid System (6 issues)
|
||||||
|
7. **#111** - Grid Click Implementation: Fix commented-out events in headless
|
||||||
|
8. **Tile Coordinate Conversion**: Convert mouse to tile coordinates
|
||||||
|
9. **Nested Grid Support**: Enable clicking on grids within grids
|
||||||
|
10. **#123** - Grid Rendering Performance: Implement 256x256 subgrid system
|
||||||
|
11. **#116** - Dirty Flagging: Add dirty flag propagation from base
|
||||||
|
12. **#124** - Grid Point Animation: Enable animating individual tiles
|
||||||
|
|
||||||
|
### Python API (6 issues)
|
||||||
|
13. **Regenerate Python Bindings**: Create consistent interface generation
|
||||||
|
14. **#109** - Vector Class Enhancement: Add [0], [1] indexing to vectors
|
||||||
|
15. **#112** - Fix Object Splitting: Preserve Python derived class types
|
||||||
|
16. **#101/#110** - Standardize Constructors: Make all constructors consistent
|
||||||
|
17. **Color Class Bindings**: Properly expose SFML Color class
|
||||||
|
18. **Font Class Bindings**: Properly expose SFML Font class
|
||||||
|
|
||||||
|
### Architecture (8 issues)
|
||||||
|
19. **#118** - Scene as Drawable: Refactor Scene to inherit from Drawable
|
||||||
|
20. **Scene Visibility System**: Implement exclusive visibility switching
|
||||||
|
21. **Replace Transition System**: Use animations not special transitions
|
||||||
|
22. **#122** - Parent-Child UI: Add parent field to UI drawables
|
||||||
|
23. **Collection Methods**: Implement append/remove/extend with parent updates
|
||||||
|
24. **#121** - Timer Object System: Replace string-dictionary timers
|
||||||
|
25. **One-Shot Timer Mode**: Implement proper one-shot functionality
|
||||||
|
26. **Button Mechanics**: Any entity type can trigger buttons
|
||||||
|
|
||||||
|
### Entity System (4 issues)
|
||||||
|
27. **Step-On Entities**: Implement trigger when stepped on
|
||||||
|
28. **Bump Interaction**: Add bump-to-interact behavior
|
||||||
|
29. **Type-Aware Interactions**: Entity interactions based on type
|
||||||
|
30. **Button Mechanics**: Any entity can trigger buttons
|
||||||
|
|
||||||
|
### Tutorial & Documentation (4 issues)
|
||||||
|
31. **Fix Part 2 Tutorial**: Unblock with animation fixes
|
||||||
|
32. **Regenerate Parts 3-6**: Replace machine-generated content
|
||||||
|
33. **API Documentation**: Document ergonomic improvements
|
||||||
|
34. **Tutorial Alignment**: Ensure parts match TCOD structure
|
||||||
|
|
||||||
|
---
|
||||||
|
|
||||||
|
*Last Updated: 2025-07-12 (CRITICAL TUTORIAL SPRINT)*
|
||||||
|
*Next Review: July 15 after event start*
|
|
@ -1,7 +1,5 @@
|
||||||
# McRogueFace API Reference
|
# McRogueFace API Reference
|
||||||
|
|
||||||
*Generated on 2025-07-15 21:28:42*
|
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
McRogueFace Python API
|
McRogueFace Python API
|
||||||
|
@ -375,6 +373,14 @@ A rectangular frame UI element that can contain other drawable elements.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `get_bounds()`
|
||||||
|
|
||||||
|
Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
||||||
|
|
||||||
|
**Note:** The bounds are in screen coordinates and account for current position and size.
|
||||||
|
|
||||||
#### `resize(width, height)`
|
#### `resize(width, height)`
|
||||||
|
|
||||||
Resize the element to new dimensions.
|
Resize the element to new dimensions.
|
||||||
|
@ -395,14 +401,6 @@ Move the element by a relative offset.
|
||||||
|
|
||||||
**Note:** This modifies the x and y position properties by the given amounts.
|
**Note:** This modifies the x and y position properties by the given amounts.
|
||||||
|
|
||||||
#### `get_bounds()`
|
|
||||||
|
|
||||||
Get the bounding rectangle of this drawable element.
|
|
||||||
|
|
||||||
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
|
||||||
|
|
||||||
**Note:** The bounds are in screen coordinates and account for current position and size.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `Caption`
|
### class `Caption`
|
||||||
|
@ -411,6 +409,14 @@ A text display UI element with customizable font and styling.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `get_bounds()`
|
||||||
|
|
||||||
|
Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
||||||
|
|
||||||
|
**Note:** The bounds are in screen coordinates and account for current position and size.
|
||||||
|
|
||||||
#### `resize(width, height)`
|
#### `resize(width, height)`
|
||||||
|
|
||||||
Resize the element to new dimensions.
|
Resize the element to new dimensions.
|
||||||
|
@ -431,14 +437,6 @@ Move the element by a relative offset.
|
||||||
|
|
||||||
**Note:** This modifies the x and y position properties by the given amounts.
|
**Note:** This modifies the x and y position properties by the given amounts.
|
||||||
|
|
||||||
#### `get_bounds()`
|
|
||||||
|
|
||||||
Get the bounding rectangle of this drawable element.
|
|
||||||
|
|
||||||
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
|
||||||
|
|
||||||
**Note:** The bounds are in screen coordinates and account for current position and size.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `Sprite`
|
### class `Sprite`
|
||||||
|
@ -447,6 +445,14 @@ A sprite UI element that displays a texture or portion of a texture atlas.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `get_bounds()`
|
||||||
|
|
||||||
|
Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
||||||
|
|
||||||
|
**Note:** The bounds are in screen coordinates and account for current position and size.
|
||||||
|
|
||||||
#### `resize(width, height)`
|
#### `resize(width, height)`
|
||||||
|
|
||||||
Resize the element to new dimensions.
|
Resize the element to new dimensions.
|
||||||
|
@ -467,14 +473,6 @@ Move the element by a relative offset.
|
||||||
|
|
||||||
**Note:** This modifies the x and y position properties by the given amounts.
|
**Note:** This modifies the x and y position properties by the given amounts.
|
||||||
|
|
||||||
#### `get_bounds()`
|
|
||||||
|
|
||||||
Get the bounding rectangle of this drawable element.
|
|
||||||
|
|
||||||
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
|
||||||
|
|
||||||
**Note:** The bounds are in screen coordinates and account for current position and size.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `Grid`
|
### class `Grid`
|
||||||
|
@ -483,16 +481,6 @@ A grid-based tilemap UI element for rendering tile-based levels and game worlds.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
#### `resize(width, height)`
|
|
||||||
|
|
||||||
Resize the element to new dimensions.
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
- `width` (*float*): New width in pixels
|
|
||||||
- `height` (*float*): New height in pixels
|
|
||||||
|
|
||||||
**Note:** For Caption and Sprite, this may not change actual size if determined by content.
|
|
||||||
|
|
||||||
#### `at(x, y)`
|
#### `at(x, y)`
|
||||||
|
|
||||||
Get the GridPoint at the specified grid coordinates.
|
Get the GridPoint at the specified grid coordinates.
|
||||||
|
@ -503,6 +491,24 @@ Get the GridPoint at the specified grid coordinates.
|
||||||
|
|
||||||
**Returns:** GridPoint or None: The grid point at (x, y), or None if out of bounds
|
**Returns:** GridPoint or None: The grid point at (x, y), or None if out of bounds
|
||||||
|
|
||||||
|
#### `get_bounds()`
|
||||||
|
|
||||||
|
Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
||||||
|
|
||||||
|
**Note:** The bounds are in screen coordinates and account for current position and size.
|
||||||
|
|
||||||
|
#### `resize(width, height)`
|
||||||
|
|
||||||
|
Resize the element to new dimensions.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
- `width` (*float*): New width in pixels
|
||||||
|
- `height` (*float*): New height in pixels
|
||||||
|
|
||||||
|
**Note:** For Caption and Sprite, this may not change actual size if determined by content.
|
||||||
|
|
||||||
#### `move(dx, dy)`
|
#### `move(dx, dy)`
|
||||||
|
|
||||||
Move the element by a relative offset.
|
Move the element by a relative offset.
|
||||||
|
@ -513,14 +519,6 @@ Move the element by a relative offset.
|
||||||
|
|
||||||
**Note:** This modifies the x and y position properties by the given amounts.
|
**Note:** This modifies the x and y position properties by the given amounts.
|
||||||
|
|
||||||
#### `get_bounds()`
|
|
||||||
|
|
||||||
Get the bounding rectangle of this drawable element.
|
|
||||||
|
|
||||||
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
|
||||||
|
|
||||||
**Note:** The bounds are in screen coordinates and account for current position and size.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `Entity`
|
### class `Entity`
|
||||||
|
@ -529,6 +527,12 @@ Game entity that can be placed in a Grid.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `die()`
|
||||||
|
|
||||||
|
Remove this entity from its parent grid.
|
||||||
|
|
||||||
|
**Note:** The entity object remains valid but is no longer rendered or updated.
|
||||||
|
|
||||||
#### `move(dx, dy)`
|
#### `move(dx, dy)`
|
||||||
|
|
||||||
Move the element by a relative offset.
|
Move the element by a relative offset.
|
||||||
|
@ -557,11 +561,11 @@ Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
**Note:** The bounds are in screen coordinates and account for current position and size.
|
**Note:** The bounds are in screen coordinates and account for current position and size.
|
||||||
|
|
||||||
#### `die()`
|
#### `index()`
|
||||||
|
|
||||||
Remove this entity from its parent grid.
|
Get the index of this entity in its parent grid's entity list.
|
||||||
|
|
||||||
**Note:** The entity object remains valid but is no longer rendered or updated.
|
**Returns:** int: Index position, or -1 if not in a grid
|
||||||
|
|
||||||
#### `resize(width, height)`
|
#### `resize(width, height)`
|
||||||
|
|
||||||
|
@ -573,12 +577,6 @@ Resize the element to new dimensions.
|
||||||
|
|
||||||
**Note:** For Caption and Sprite, this may not change actual size if determined by content.
|
**Note:** For Caption and Sprite, this may not change actual size if determined by content.
|
||||||
|
|
||||||
#### `index()`
|
|
||||||
|
|
||||||
Get the index of this entity in its parent grid's entity list.
|
|
||||||
|
|
||||||
**Returns:** int: Index position, or -1 if not in a grid
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### Collections
|
### Collections
|
||||||
|
@ -589,6 +587,13 @@ Container for Entity objects in a Grid. Supports iteration and indexing.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `append(entity)`
|
||||||
|
|
||||||
|
Add an entity to the end of the collection.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
- `entity` (*Entity*): The entity to add
|
||||||
|
|
||||||
#### `remove(entity)`
|
#### `remove(entity)`
|
||||||
|
|
||||||
Remove the first occurrence of an entity from the collection.
|
Remove the first occurrence of an entity from the collection.
|
||||||
|
@ -598,13 +603,6 @@ Remove the first occurrence of an entity from the collection.
|
||||||
|
|
||||||
**Raises:** ValueError: If entity is not in collection
|
**Raises:** ValueError: If entity is not in collection
|
||||||
|
|
||||||
#### `extend(iterable)`
|
|
||||||
|
|
||||||
Add all entities from an iterable to the collection.
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
- `iterable` (*Iterable[Entity]*): Entities to add
|
|
||||||
|
|
||||||
#### `count(entity)`
|
#### `count(entity)`
|
||||||
|
|
||||||
Count the number of occurrences of an entity in the collection.
|
Count the number of occurrences of an entity in the collection.
|
||||||
|
@ -625,12 +623,12 @@ Find the index of the first occurrence of an entity.
|
||||||
|
|
||||||
**Raises:** ValueError: If entity is not in collection
|
**Raises:** ValueError: If entity is not in collection
|
||||||
|
|
||||||
#### `append(entity)`
|
#### `extend(iterable)`
|
||||||
|
|
||||||
Add an entity to the end of the collection.
|
Add all entities from an iterable to the collection.
|
||||||
|
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
- `entity` (*Entity*): The entity to add
|
- `iterable` (*Iterable[Entity]*): Entities to add
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -640,6 +638,13 @@ Container for UI drawable elements. Supports iteration and indexing.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `append(drawable)`
|
||||||
|
|
||||||
|
Add a drawable element to the end of the collection.
|
||||||
|
|
||||||
|
**Arguments:**
|
||||||
|
- `drawable` (*UIDrawable*): The drawable element to add
|
||||||
|
|
||||||
#### `remove(drawable)`
|
#### `remove(drawable)`
|
||||||
|
|
||||||
Remove the first occurrence of a drawable from the collection.
|
Remove the first occurrence of a drawable from the collection.
|
||||||
|
@ -649,13 +654,6 @@ Remove the first occurrence of a drawable from the collection.
|
||||||
|
|
||||||
**Raises:** ValueError: If drawable is not in collection
|
**Raises:** ValueError: If drawable is not in collection
|
||||||
|
|
||||||
#### `extend(iterable)`
|
|
||||||
|
|
||||||
Add all drawables from an iterable to the collection.
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
- `iterable` (*Iterable[UIDrawable]*): Drawables to add
|
|
||||||
|
|
||||||
#### `count(drawable)`
|
#### `count(drawable)`
|
||||||
|
|
||||||
Count the number of occurrences of a drawable in the collection.
|
Count the number of occurrences of a drawable in the collection.
|
||||||
|
@ -676,12 +674,12 @@ Find the index of the first occurrence of a drawable.
|
||||||
|
|
||||||
**Raises:** ValueError: If drawable is not in collection
|
**Raises:** ValueError: If drawable is not in collection
|
||||||
|
|
||||||
#### `append(drawable)`
|
#### `extend(iterable)`
|
||||||
|
|
||||||
Add a drawable element to the end of the collection.
|
Add all drawables from an iterable to the collection.
|
||||||
|
|
||||||
**Arguments:**
|
**Arguments:**
|
||||||
- `drawable` (*UIDrawable*): The drawable element to add
|
- `iterable` (*Iterable[UIDrawable]*): Drawables to add
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
|
@ -705,17 +703,6 @@ RGBA color representation.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
#### `to_hex()`
|
|
||||||
|
|
||||||
Convert this Color to a hexadecimal string.
|
|
||||||
|
|
||||||
**Returns:** str: Hex color string in format "#RRGGBB"
|
|
||||||
|
|
||||||
**Example:**
|
|
||||||
```python
|
|
||||||
hex_str = color.to_hex() # Returns "#FF0000"
|
|
||||||
```
|
|
||||||
|
|
||||||
#### `from_hex(hex_string)`
|
#### `from_hex(hex_string)`
|
||||||
|
|
||||||
Create a Color from a hexadecimal color string.
|
Create a Color from a hexadecimal color string.
|
||||||
|
@ -730,6 +717,17 @@ Create a Color from a hexadecimal color string.
|
||||||
red = Color.from_hex("#FF0000")
|
red = Color.from_hex("#FF0000")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### `to_hex()`
|
||||||
|
|
||||||
|
Convert this Color to a hexadecimal string.
|
||||||
|
|
||||||
|
**Returns:** str: Hex color string in format "#RRGGBB"
|
||||||
|
|
||||||
|
**Example:**
|
||||||
|
```python
|
||||||
|
hex_str = color.to_hex() # Returns "#FF0000"
|
||||||
|
```
|
||||||
|
|
||||||
#### `lerp(other, t)`
|
#### `lerp(other, t)`
|
||||||
|
|
||||||
Linearly interpolate between this color and another.
|
Linearly interpolate between this color and another.
|
||||||
|
@ -759,23 +757,6 @@ Calculate the length/magnitude of this vector.
|
||||||
|
|
||||||
**Returns:** float: The magnitude of the vector
|
**Returns:** float: The magnitude of the vector
|
||||||
|
|
||||||
#### `normalize()`
|
|
||||||
|
|
||||||
Return a unit vector in the same direction.
|
|
||||||
|
|
||||||
**Returns:** Vector: New normalized vector with magnitude 1.0
|
|
||||||
|
|
||||||
**Raises:** ValueError: If vector has zero magnitude
|
|
||||||
|
|
||||||
#### `dot(other)`
|
|
||||||
|
|
||||||
Calculate the dot product with another vector.
|
|
||||||
|
|
||||||
**Arguments:**
|
|
||||||
- `other` (*Vector*): The other vector
|
|
||||||
|
|
||||||
**Returns:** float: Dot product of the two vectors
|
|
||||||
|
|
||||||
#### `distance_to(other)`
|
#### `distance_to(other)`
|
||||||
|
|
||||||
Calculate the distance to another vector.
|
Calculate the distance to another vector.
|
||||||
|
@ -785,11 +766,14 @@ Calculate the distance to another vector.
|
||||||
|
|
||||||
**Returns:** float: Distance between the two vectors
|
**Returns:** float: Distance between the two vectors
|
||||||
|
|
||||||
#### `copy()`
|
#### `dot(other)`
|
||||||
|
|
||||||
Create a copy of this vector.
|
Calculate the dot product with another vector.
|
||||||
|
|
||||||
**Returns:** Vector: New Vector object with same x and y values
|
**Arguments:**
|
||||||
|
- `other` (*Vector*): The other vector
|
||||||
|
|
||||||
|
**Returns:** float: Dot product of the two vectors
|
||||||
|
|
||||||
#### `angle()`
|
#### `angle()`
|
||||||
|
|
||||||
|
@ -805,6 +789,20 @@ Calculate the squared magnitude of this vector.
|
||||||
|
|
||||||
**Note:** Use this for comparisons to avoid expensive square root calculation.
|
**Note:** Use this for comparisons to avoid expensive square root calculation.
|
||||||
|
|
||||||
|
#### `copy()`
|
||||||
|
|
||||||
|
Create a copy of this vector.
|
||||||
|
|
||||||
|
**Returns:** Vector: New Vector object with same x and y values
|
||||||
|
|
||||||
|
#### `normalize()`
|
||||||
|
|
||||||
|
Return a unit vector in the same direction.
|
||||||
|
|
||||||
|
**Returns:** Vector: New normalized vector with magnitude 1.0
|
||||||
|
|
||||||
|
**Raises:** ValueError: If vector has zero magnitude
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `Texture`
|
### class `Texture`
|
||||||
|
@ -836,12 +834,6 @@ Animate UI element properties over time.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
#### `get_current_value()`
|
|
||||||
|
|
||||||
Get the current interpolated value of the animation.
|
|
||||||
|
|
||||||
**Returns:** float: Current animation value between start and end
|
|
||||||
|
|
||||||
#### `update(delta_time)`
|
#### `update(delta_time)`
|
||||||
|
|
||||||
Update the animation by the given time delta.
|
Update the animation by the given time delta.
|
||||||
|
@ -860,6 +852,12 @@ Start the animation on a target UI element.
|
||||||
|
|
||||||
**Note:** The target must have the property specified in the animation constructor.
|
**Note:** The target must have the property specified in the animation constructor.
|
||||||
|
|
||||||
|
#### `get_current_value()`
|
||||||
|
|
||||||
|
Get the current interpolated value of the animation.
|
||||||
|
|
||||||
|
**Returns:** float: Current animation value between start and end
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `Drawable`
|
### class `Drawable`
|
||||||
|
@ -868,6 +866,14 @@ Base class for all drawable UI elements.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `get_bounds()`
|
||||||
|
|
||||||
|
Get the bounding rectangle of this drawable element.
|
||||||
|
|
||||||
|
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
||||||
|
|
||||||
|
**Note:** The bounds are in screen coordinates and account for current position and size.
|
||||||
|
|
||||||
#### `resize(width, height)`
|
#### `resize(width, height)`
|
||||||
|
|
||||||
Resize the element to new dimensions.
|
Resize the element to new dimensions.
|
||||||
|
@ -888,14 +894,6 @@ Move the element by a relative offset.
|
||||||
|
|
||||||
**Note:** This modifies the x and y position properties by the given amounts.
|
**Note:** This modifies the x and y position properties by the given amounts.
|
||||||
|
|
||||||
#### `get_bounds()`
|
|
||||||
|
|
||||||
Get the bounding rectangle of this drawable element.
|
|
||||||
|
|
||||||
**Returns:** tuple: (x, y, width, height) representing the element's bounds
|
|
||||||
|
|
||||||
**Note:** The bounds are in screen coordinates and account for current position and size.
|
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `GridPoint`
|
### class `GridPoint`
|
||||||
|
@ -947,18 +945,18 @@ def handle_keyboard(key, action):
|
||||||
scene.register_keyboard(handle_keyboard)
|
scene.register_keyboard(handle_keyboard)
|
||||||
```
|
```
|
||||||
|
|
||||||
#### `get_ui()`
|
|
||||||
|
|
||||||
Get the UI element collection for this scene.
|
|
||||||
|
|
||||||
**Returns:** UICollection: Collection of all UI elements in this scene
|
|
||||||
|
|
||||||
#### `activate()`
|
#### `activate()`
|
||||||
|
|
||||||
Make this scene the active scene.
|
Make this scene the active scene.
|
||||||
|
|
||||||
**Note:** Equivalent to calling setScene() with this scene's name.
|
**Note:** Equivalent to calling setScene() with this scene's name.
|
||||||
|
|
||||||
|
#### `get_ui()`
|
||||||
|
|
||||||
|
Get the UI element collection for this scene.
|
||||||
|
|
||||||
|
**Returns:** UICollection: Collection of all UI elements in this scene
|
||||||
|
|
||||||
#### `keypress(handler)`
|
#### `keypress(handler)`
|
||||||
|
|
||||||
Register a keyboard handler function for this scene.
|
Register a keyboard handler function for this scene.
|
||||||
|
@ -976,18 +974,6 @@ Timer object for scheduled callbacks.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
#### `pause()`
|
|
||||||
|
|
||||||
Pause the timer, stopping its callback execution.
|
|
||||||
|
|
||||||
**Note:** Use resume() to continue the timer from where it was paused.
|
|
||||||
|
|
||||||
#### `resume()`
|
|
||||||
|
|
||||||
Resume a paused timer.
|
|
||||||
|
|
||||||
**Note:** Has no effect if timer is not paused.
|
|
||||||
|
|
||||||
#### `restart()`
|
#### `restart()`
|
||||||
|
|
||||||
Restart the timer from the beginning.
|
Restart the timer from the beginning.
|
||||||
|
@ -1000,6 +986,18 @@ Cancel the timer and remove it from the system.
|
||||||
|
|
||||||
**Note:** After cancelling, the timer object cannot be reused.
|
**Note:** After cancelling, the timer object cannot be reused.
|
||||||
|
|
||||||
|
#### `pause()`
|
||||||
|
|
||||||
|
Pause the timer, stopping its callback execution.
|
||||||
|
|
||||||
|
**Note:** Use resume() to continue the timer from where it was paused.
|
||||||
|
|
||||||
|
#### `resume()`
|
||||||
|
|
||||||
|
Resume a paused timer.
|
||||||
|
|
||||||
|
**Note:** Has no effect if timer is not paused.
|
||||||
|
|
||||||
---
|
---
|
||||||
|
|
||||||
### class `Window`
|
### class `Window`
|
||||||
|
@ -1008,6 +1006,14 @@ Window singleton for accessing and modifying the game window properties.
|
||||||
|
|
||||||
#### Methods
|
#### Methods
|
||||||
|
|
||||||
|
#### `get()`
|
||||||
|
|
||||||
|
Get the Window singleton instance.
|
||||||
|
|
||||||
|
**Returns:** Window: The singleton window object
|
||||||
|
|
||||||
|
**Note:** This is a static method that returns the same instance every time.
|
||||||
|
|
||||||
#### `screenshot(filename)`
|
#### `screenshot(filename)`
|
||||||
|
|
||||||
Take a screenshot and save it to a file.
|
Take a screenshot and save it to a file.
|
||||||
|
@ -1017,14 +1023,6 @@ Take a screenshot and save it to a file.
|
||||||
|
|
||||||
**Note:** Supports PNG, JPG, and BMP formats based on file extension.
|
**Note:** Supports PNG, JPG, and BMP formats based on file extension.
|
||||||
|
|
||||||
#### `get()`
|
|
||||||
|
|
||||||
Get the Window singleton instance.
|
|
||||||
|
|
||||||
**Returns:** Window: The singleton window object
|
|
||||||
|
|
||||||
**Note:** This is a static method that returns the same instance every time.
|
|
||||||
|
|
||||||
#### `center()`
|
#### `center()`
|
||||||
|
|
||||||
Center the window on the screen.
|
Center the window on the screen.
|
||||||
|
|
|
@ -108,7 +108,7 @@
|
||||||
<body>
|
<body>
|
||||||
<div class="container">
|
<div class="container">
|
||||||
<h1>McRogueFace API Reference</h1>
|
<h1>McRogueFace API Reference</h1>
|
||||||
<p><em>Generated on 2025-07-15 21:28:24</em></p>
|
<p><em>Generated on 2025-07-10 01:13:53</em></p>
|
||||||
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
|
<p><em>This documentation was dynamically generated from the compiled module.</em></p>
|
||||||
|
|
||||||
<div class="toc">
|
<div class="toc">
|
||||||
|
@ -350,31 +350,14 @@ Note:</p>
|
||||||
<p>Animation object for animating UI properties</p>
|
<p>Animation object for animating UI properties</p>
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
|
||||||
<h5><code class="method-name">completecomplete() -> None</code></h5>
|
|
||||||
<p>Complete the animation immediately by jumping to the final value.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">get_current_value(...)</code></h5>
|
<h5><code class="method-name">get_current_value(...)</code></h5>
|
||||||
<p>Get the current interpolated value</p>
|
<p>Get the current interpolated value</p>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">hasValidTargethasValidTarget() -> bool</code></h5>
|
<h5><code class="method-name">start(...)</code></h5>
|
||||||
<p>Check if the animation still has a valid target.</p>
|
<p>Start the animation on a target UIDrawable</p>
|
||||||
<p style='margin-left: 20px;'><span class='returns'>Returns:</span> True if the target still exists, False if it was destroyed.</p>
|
|
||||||
</div>
|
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
|
||||||
<h5><code class="method-name">startstart(target) -> None</code></h5>
|
|
||||||
<p>Start the animation on a target UI element.
|
|
||||||
|
|
||||||
|
|
||||||
Note:</p>
|
|
||||||
<div style='margin-left: 20px;'>
|
|
||||||
<div><span class='arg-name'>target</span>: The UI element to animate (Frame, Caption, Sprite, Grid, or Entity)</div>
|
|
||||||
</div>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
@ -385,41 +368,29 @@ Note:</p>
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<h3 id="Caption"><span class="class-name">Caption</span></h3>
|
<h3 id="Caption"><span class="class-name">Caption</span></h3>
|
||||||
<p><em>Inherits from: Drawable</em></p>
|
<p><em>Inherits from: Drawable</em></p>
|
||||||
<p>Caption(pos=None, font=None, text='', **kwargs)
|
<p>Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)
|
||||||
|
|
||||||
A text display UI element with customizable font and styling.
|
A text display UI element with customizable font and styling.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pos (tuple, optional): Position as (x, y) tuple. Default: (0, 0)
|
text (str): The text content to display. Default: ''
|
||||||
font (Font, optional): Font object for text rendering. Default: engine default font
|
x (float): X position in pixels. Default: 0
|
||||||
text (str, optional): The text content to display. Default: ''
|
y (float): Y position in pixels. Default: 0
|
||||||
|
font (Font): Font object for text rendering. Default: engine default font
|
||||||
Keyword Args:
|
|
||||||
fill_color (Color): Text fill color. Default: (255, 255, 255, 255)
|
fill_color (Color): Text fill color. Default: (255, 255, 255, 255)
|
||||||
outline_color (Color): Text outline color. Default: (0, 0, 0, 255)
|
outline_color (Color): Text outline color. Default: (0, 0, 0, 255)
|
||||||
outline (float): Text outline thickness. Default: 0
|
outline (float): Text outline thickness. Default: 0
|
||||||
font_size (float): Font size in points. Default: 16
|
|
||||||
click (callable): Click event handler. Default: None
|
click (callable): Click event handler. Default: None
|
||||||
visible (bool): Visibility state. Default: True
|
|
||||||
opacity (float): Opacity (0.0-1.0). Default: 1.0
|
|
||||||
z_index (int): Rendering order. Default: 0
|
|
||||||
name (str): Element name for finding. Default: None
|
|
||||||
x (float): X position override. Default: 0
|
|
||||||
y (float): Y position override. Default: 0
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
text (str): The displayed text content
|
text (str): The displayed text content
|
||||||
x, y (float): Position in pixels
|
x, y (float): Position in pixels
|
||||||
pos (Vector): Position as a Vector object
|
|
||||||
font (Font): Font used for rendering
|
font (Font): Font used for rendering
|
||||||
font_size (float): Font size in points
|
|
||||||
fill_color, outline_color (Color): Text appearance
|
fill_color, outline_color (Color): Text appearance
|
||||||
outline (float): Outline thickness
|
outline (float): Outline thickness
|
||||||
click (callable): Click event handler
|
click (callable): Click event handler
|
||||||
visible (bool): Visibility state
|
visible (bool): Visibility state
|
||||||
opacity (float): Opacity value
|
|
||||||
z_index (int): Rendering order
|
z_index (int): Rendering order
|
||||||
name (str): Element name
|
|
||||||
w, h (float): Read-only computed size based on text and font</p>
|
w, h (float): Read-only computed size based on text and font</p>
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
|
@ -476,32 +447,8 @@ Attributes:
|
||||||
|
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<h3 id="Entity"><span class="class-name">Entity</span></h3>
|
<h3 id="Entity"><span class="class-name">Entity</span></h3>
|
||||||
<p>Entity(grid_pos=None, texture=None, sprite_index=0, **kwargs)
|
<p><em>Inherits from: Drawable</em></p>
|
||||||
|
<p>UIEntity objects</p>
|
||||||
A game entity that exists on a grid with sprite rendering.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
grid_pos (tuple, optional): Grid position as (x, y) tuple. Default: (0, 0)
|
|
||||||
texture (Texture, optional): Texture object for sprite. Default: default texture
|
|
||||||
sprite_index (int, optional): Index into texture atlas. Default: 0
|
|
||||||
|
|
||||||
Keyword Args:
|
|
||||||
grid (Grid): Grid to attach entity to. Default: None
|
|
||||||
visible (bool): Visibility state. Default: True
|
|
||||||
opacity (float): Opacity (0.0-1.0). Default: 1.0
|
|
||||||
name (str): Element name for finding. Default: None
|
|
||||||
x (float): X grid position override. Default: 0
|
|
||||||
y (float): Y grid position override. Default: 0
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
pos (tuple): Grid position as (x, y) tuple
|
|
||||||
x, y (float): Grid position coordinates
|
|
||||||
draw_pos (tuple): Pixel position for rendering
|
|
||||||
gridstate (GridPointState): Visibility state for grid points
|
|
||||||
sprite_index (int): Current sprite index
|
|
||||||
visible (bool): Visibility state
|
|
||||||
opacity (float): Opacity value
|
|
||||||
name (str): Element name</p>
|
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
@ -585,42 +532,30 @@ when the entity moves if it has a grid with perspective set.</p>
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<h3 id="Frame"><span class="class-name">Frame</span></h3>
|
<h3 id="Frame"><span class="class-name">Frame</span></h3>
|
||||||
<p><em>Inherits from: Drawable</em></p>
|
<p><em>Inherits from: Drawable</em></p>
|
||||||
<p>Frame(pos=None, size=None, **kwargs)
|
<p>Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)
|
||||||
|
|
||||||
A rectangular frame UI element that can contain other drawable elements.
|
A rectangular frame UI element that can contain other drawable elements.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pos (tuple, optional): Position as (x, y) tuple. Default: (0, 0)
|
x (float): X position in pixels. Default: 0
|
||||||
size (tuple, optional): Size as (width, height) tuple. Default: (0, 0)
|
y (float): Y position in pixels. Default: 0
|
||||||
|
w (float): Width in pixels. Default: 0
|
||||||
Keyword Args:
|
h (float): Height in pixels. Default: 0
|
||||||
fill_color (Color): Background fill color. Default: (0, 0, 0, 128)
|
fill_color (Color): Background fill color. Default: (0, 0, 0, 128)
|
||||||
outline_color (Color): Border outline color. Default: (255, 255, 255, 255)
|
outline_color (Color): Border outline color. Default: (255, 255, 255, 255)
|
||||||
outline (float): Border outline thickness. Default: 0
|
outline (float): Border outline thickness. Default: 0
|
||||||
click (callable): Click event handler. Default: None
|
click (callable): Click event handler. Default: None
|
||||||
children (list): Initial list of child drawable elements. Default: None
|
children (list): Initial list of child drawable elements. Default: None
|
||||||
visible (bool): Visibility state. Default: True
|
|
||||||
opacity (float): Opacity (0.0-1.0). Default: 1.0
|
|
||||||
z_index (int): Rendering order. Default: 0
|
|
||||||
name (str): Element name for finding. Default: None
|
|
||||||
x (float): X position override. Default: 0
|
|
||||||
y (float): Y position override. Default: 0
|
|
||||||
w (float): Width override. Default: 0
|
|
||||||
h (float): Height override. Default: 0
|
|
||||||
clip_children (bool): Whether to clip children to frame bounds. Default: False
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
x, y (float): Position in pixels
|
x, y (float): Position in pixels
|
||||||
w, h (float): Size in pixels
|
w, h (float): Size in pixels
|
||||||
pos (Vector): Position as a Vector object
|
|
||||||
fill_color, outline_color (Color): Visual appearance
|
fill_color, outline_color (Color): Visual appearance
|
||||||
outline (float): Border thickness
|
outline (float): Border thickness
|
||||||
click (callable): Click event handler
|
click (callable): Click event handler
|
||||||
children (list): Collection of child drawable elements
|
children (list): Collection of child drawable elements
|
||||||
visible (bool): Visibility state
|
visible (bool): Visibility state
|
||||||
opacity (float): Opacity value
|
|
||||||
z_index (int): Rendering order
|
z_index (int): Rendering order
|
||||||
name (str): Element name
|
|
||||||
clip_children (bool): Whether to clip children to frame bounds</p>
|
clip_children (bool): Whether to clip children to frame bounds</p>
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
|
@ -640,53 +575,32 @@ Attributes:
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<h3 id="Grid"><span class="class-name">Grid</span></h3>
|
<h3 id="Grid"><span class="class-name">Grid</span></h3>
|
||||||
<p><em>Inherits from: Drawable</em></p>
|
<p><em>Inherits from: Drawable</em></p>
|
||||||
<p>Grid(pos=None, size=None, grid_size=None, texture=None, **kwargs)
|
<p>Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)
|
||||||
|
|
||||||
A grid-based UI element for tile-based rendering and entity management.
|
A grid-based tilemap UI element for rendering tile-based levels and game worlds.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pos (tuple, optional): Position as (x, y) tuple. Default: (0, 0)
|
x (float): X position in pixels. Default: 0
|
||||||
size (tuple, optional): Size as (width, height) tuple. Default: auto-calculated from grid_size
|
y (float): Y position in pixels. Default: 0
|
||||||
grid_size (tuple, optional): Grid dimensions as (grid_x, grid_y) tuple. Default: (2, 2)
|
grid_size (tuple): Grid dimensions as (width, height) in tiles. Default: (20, 20)
|
||||||
texture (Texture, optional): Texture containing tile sprites. Default: default texture
|
texture (Texture): Texture atlas containing tile sprites. Default: None
|
||||||
|
tile_width (int): Width of each tile in pixels. Default: 16
|
||||||
Keyword Args:
|
tile_height (int): Height of each tile in pixels. Default: 16
|
||||||
fill_color (Color): Background fill color. Default: None
|
scale (float): Grid scaling factor. Default: 1.0
|
||||||
click (callable): Click event handler. Default: None
|
click (callable): Click event handler. Default: None
|
||||||
center_x (float): X coordinate of center point. Default: 0
|
|
||||||
center_y (float): Y coordinate of center point. Default: 0
|
|
||||||
zoom (float): Zoom level for rendering. Default: 1.0
|
|
||||||
perspective (int): Entity perspective index (-1 for omniscient). Default: -1
|
|
||||||
visible (bool): Visibility state. Default: True
|
|
||||||
opacity (float): Opacity (0.0-1.0). Default: 1.0
|
|
||||||
z_index (int): Rendering order. Default: 0
|
|
||||||
name (str): Element name for finding. Default: None
|
|
||||||
x (float): X position override. Default: 0
|
|
||||||
y (float): Y position override. Default: 0
|
|
||||||
w (float): Width override. Default: auto-calculated
|
|
||||||
h (float): Height override. Default: auto-calculated
|
|
||||||
grid_x (int): Grid width override. Default: 2
|
|
||||||
grid_y (int): Grid height override. Default: 2
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
x, y (float): Position in pixels
|
x, y (float): Position in pixels
|
||||||
w, h (float): Size in pixels
|
|
||||||
pos (Vector): Position as a Vector object
|
|
||||||
size (tuple): Size as (width, height) tuple
|
|
||||||
center (tuple): Center point as (x, y) tuple
|
|
||||||
center_x, center_y (float): Center point coordinates
|
|
||||||
zoom (float): Zoom level for rendering
|
|
||||||
grid_size (tuple): Grid dimensions (width, height) in tiles
|
grid_size (tuple): Grid dimensions (width, height) in tiles
|
||||||
grid_x, grid_y (int): Grid dimensions
|
tile_width, tile_height (int): Tile dimensions in pixels
|
||||||
texture (Texture): Tile texture atlas
|
texture (Texture): Tile texture atlas
|
||||||
fill_color (Color): Background color
|
scale (float): Scale multiplier
|
||||||
entities (EntityCollection): Collection of entities in the grid
|
points (list): 2D array of GridPoint objects for tile data
|
||||||
perspective (int): Entity perspective index
|
entities (list): Collection of Entity objects in the grid
|
||||||
|
background_color (Color): Grid background color
|
||||||
click (callable): Click event handler
|
click (callable): Click event handler
|
||||||
visible (bool): Visibility state
|
visible (bool): Visibility state
|
||||||
opacity (float): Opacity value
|
z_index (int): Rendering order</p>
|
||||||
z_index (int): Rendering order
|
|
||||||
name (str): Element name</p>
|
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
|
@ -819,39 +733,26 @@ Attributes:
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<h3 id="Sprite"><span class="class-name">Sprite</span></h3>
|
<h3 id="Sprite"><span class="class-name">Sprite</span></h3>
|
||||||
<p><em>Inherits from: Drawable</em></p>
|
<p><em>Inherits from: Drawable</em></p>
|
||||||
<p>Sprite(pos=None, texture=None, sprite_index=0, **kwargs)
|
<p>Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)
|
||||||
|
|
||||||
A sprite UI element that displays a texture or portion of a texture atlas.
|
A sprite UI element that displays a texture or portion of a texture atlas.
|
||||||
|
|
||||||
Args:
|
Args:
|
||||||
pos (tuple, optional): Position as (x, y) tuple. Default: (0, 0)
|
x (float): X position in pixels. Default: 0
|
||||||
texture (Texture, optional): Texture object to display. Default: default texture
|
y (float): Y position in pixels. Default: 0
|
||||||
sprite_index (int, optional): Index into texture atlas. Default: 0
|
texture (Texture): Texture object to display. Default: None
|
||||||
|
sprite_index (int): Index into texture atlas (if applicable). Default: 0
|
||||||
Keyword Args:
|
scale (float): Sprite scaling factor. Default: 1.0
|
||||||
scale (float): Uniform scale factor. Default: 1.0
|
|
||||||
scale_x (float): Horizontal scale factor. Default: 1.0
|
|
||||||
scale_y (float): Vertical scale factor. Default: 1.0
|
|
||||||
click (callable): Click event handler. Default: None
|
click (callable): Click event handler. Default: None
|
||||||
visible (bool): Visibility state. Default: True
|
|
||||||
opacity (float): Opacity (0.0-1.0). Default: 1.0
|
|
||||||
z_index (int): Rendering order. Default: 0
|
|
||||||
name (str): Element name for finding. Default: None
|
|
||||||
x (float): X position override. Default: 0
|
|
||||||
y (float): Y position override. Default: 0
|
|
||||||
|
|
||||||
Attributes:
|
Attributes:
|
||||||
x, y (float): Position in pixels
|
x, y (float): Position in pixels
|
||||||
pos (Vector): Position as a Vector object
|
|
||||||
texture (Texture): The texture being displayed
|
texture (Texture): The texture being displayed
|
||||||
sprite_index (int): Current sprite index in texture atlas
|
sprite_index (int): Current sprite index in texture atlas
|
||||||
scale (float): Uniform scale factor
|
scale (float): Scale multiplier
|
||||||
scale_x, scale_y (float): Individual scale factors
|
|
||||||
click (callable): Click event handler
|
click (callable): Click event handler
|
||||||
visible (bool): Visibility state
|
visible (bool): Visibility state
|
||||||
opacity (float): Opacity value
|
|
||||||
z_index (int): Rendering order
|
z_index (int): Rendering order
|
||||||
name (str): Element name
|
|
||||||
w, h (float): Read-only computed size based on texture and scale</p>
|
w, h (float): Read-only computed size based on texture and scale</p>
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
|
@ -876,64 +777,27 @@ Attributes:
|
||||||
|
|
||||||
<div class="method-section">
|
<div class="method-section">
|
||||||
<h3 id="Timer"><span class="class-name">Timer</span></h3>
|
<h3 id="Timer"><span class="class-name">Timer</span></h3>
|
||||||
<p>Timer(name, callback, interval, once=False)
|
<p>Timer object for scheduled callbacks</p>
|
||||||
|
|
||||||
Create a timer that calls a function at regular intervals.
|
|
||||||
|
|
||||||
Args:
|
|
||||||
name (str): Unique identifier for the timer
|
|
||||||
callback (callable): Function to call - receives (timer, runtime) args
|
|
||||||
interval (int): Time between calls in milliseconds
|
|
||||||
once (bool): If True, timer stops after first call. Default: False
|
|
||||||
|
|
||||||
Attributes:
|
|
||||||
interval (int): Time between calls in milliseconds
|
|
||||||
remaining (int): Time until next call in milliseconds (read-only)
|
|
||||||
paused (bool): Whether timer is paused (read-only)
|
|
||||||
active (bool): Whether timer is active and not paused (read-only)
|
|
||||||
callback (callable): The callback function
|
|
||||||
once (bool): Whether timer stops after firing once
|
|
||||||
|
|
||||||
Methods:
|
|
||||||
pause(): Pause the timer, preserving time remaining
|
|
||||||
resume(): Resume a paused timer
|
|
||||||
cancel(): Stop and remove the timer
|
|
||||||
restart(): Reset timer to start from beginning
|
|
||||||
|
|
||||||
Example:
|
|
||||||
def on_timer(timer, runtime):
|
|
||||||
print(f'Timer {timer} fired at {runtime}ms')
|
|
||||||
if runtime > 5000:
|
|
||||||
timer.cancel()
|
|
||||||
|
|
||||||
timer = mcrfpy.Timer('my_timer', on_timer, 1000)
|
|
||||||
timer.pause() # Pause timer
|
|
||||||
timer.resume() # Resume timer
|
|
||||||
timer.once = True # Make it one-shot</p>
|
|
||||||
<h4>Methods:</h4>
|
<h4>Methods:</h4>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">cancelcancel() -> None</code></h5>
|
<h5><code class="method-name">cancel(...)</code></h5>
|
||||||
<p>Cancel the timer and remove it from the timer system.
|
<p>Cancel the timer and remove it from the system</p>
|
||||||
The timer will no longer fire and cannot be restarted.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">pausepause() -> None</code></h5>
|
<h5><code class="method-name">pause(...)</code></h5>
|
||||||
<p>Pause the timer, preserving the time remaining until next trigger.
|
<p>Pause the timer</p>
|
||||||
The timer can be resumed later with resume().</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">restartrestart() -> None</code></h5>
|
<h5><code class="method-name">restart(...)</code></h5>
|
||||||
<p>Restart the timer from the beginning.
|
<p>Restart the timer from the current time</p>
|
||||||
Resets the timer to fire after a full interval from now.</p>
|
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
<div style="margin-left: 20px; margin-bottom: 15px;">
|
<div style="margin-left: 20px; margin-bottom: 15px;">
|
||||||
<h5><code class="method-name">resumeresume() -> None</code></h5>
|
<h5><code class="method-name">resume(...)</code></h5>
|
||||||
<p>Resume a paused timer from where it left off.
|
<p>Resume a paused timer</p>
|
||||||
Has no effect if the timer is not paused.</p>
|
|
||||||
</div>
|
</div>
|
||||||
</div>
|
</div>
|
||||||
|
|
||||||
|
|
|
@ -1,532 +1,209 @@
|
||||||
"""Type stubs for McRogueFace Python API.
|
"""Type stubs for McRogueFace Python API.
|
||||||
|
|
||||||
Core game engine interface for creating roguelike games with Python.
|
Auto-generated - do not edit directly.
|
||||||
"""
|
"""
|
||||||
|
|
||||||
from typing import Any, List, Dict, Tuple, Optional, Callable, Union, overload
|
from typing import Any, List, Dict, Tuple, Optional, Callable, Union
|
||||||
|
|
||||||
# Type aliases
|
# Module documentation
|
||||||
UIElement = Union['Frame', 'Caption', 'Sprite', 'Grid']
|
# McRogueFace Python API\n\nCore game engine interface for creating roguelike games with Python.\n\nThis module provides:\n- Scene management (createScene, setScene, currentScene)\n- UI components (Frame, Caption, Sprite, Grid)\n- Entity system for game objects\n- Audio playback (sound effects and music)\n- Timer system for scheduled events\n- Input handling\n- Performance metrics\n\nExample:\n import mcrfpy\n \n # Create a new scene\n mcrfpy.createScene('game')\n mcrfpy.setScene('game')\n \n # Add UI elements\n frame = mcrfpy.Frame(10, 10, 200, 100)\n caption = mcrfpy.Caption('Hello World', 50, 50)\n mcrfpy.sceneUI().extend([frame, caption])\n
|
||||||
Transition = Union[str, None]
|
|
||||||
|
|
||||||
# Classes
|
# Classes
|
||||||
|
|
||||||
|
class Animation:
|
||||||
|
"""Animation object for animating UI properties"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
|
def get_current_value(self, *args, **kwargs) -> Any: ...
|
||||||
|
def start(self, *args, **kwargs) -> Any: ...
|
||||||
|
def update(selfreturns True if still running) -> Any: ...
|
||||||
|
|
||||||
|
class Caption:
|
||||||
|
"""Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
|
def get_bounds(selfx, y, width, height) -> Any: ...
|
||||||
|
def move(selfdx, dy) -> Any: ...
|
||||||
|
def resize(selfwidth, height) -> Any: ...
|
||||||
|
|
||||||
class Color:
|
class Color:
|
||||||
"""SFML Color Object for RGBA colors."""
|
"""SFML Color Object"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
r: int
|
def from_hex(selfe.g., '#FF0000' or 'FF0000') -> Any: ...
|
||||||
g: int
|
def lerp(self, *args, **kwargs) -> Any: ...
|
||||||
b: int
|
def to_hex(self, *args, **kwargs) -> Any: ...
|
||||||
a: int
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(self) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self, r: int, g: int, b: int, a: int = 255) -> None: ...
|
|
||||||
|
|
||||||
def from_hex(self, hex_string: str) -> 'Color':
|
|
||||||
"""Create color from hex string (e.g., '#FF0000' or 'FF0000')."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def to_hex(self) -> str:
|
|
||||||
"""Convert color to hex string format."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def lerp(self, other: 'Color', t: float) -> 'Color':
|
|
||||||
"""Linear interpolation between two colors."""
|
|
||||||
...
|
|
||||||
|
|
||||||
class Vector:
|
|
||||||
"""SFML Vector Object for 2D coordinates."""
|
|
||||||
|
|
||||||
x: float
|
|
||||||
y: float
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(self) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self, x: float, y: float) -> None: ...
|
|
||||||
|
|
||||||
def add(self, other: 'Vector') -> 'Vector': ...
|
|
||||||
def subtract(self, other: 'Vector') -> 'Vector': ...
|
|
||||||
def multiply(self, scalar: float) -> 'Vector': ...
|
|
||||||
def divide(self, scalar: float) -> 'Vector': ...
|
|
||||||
def distance(self, other: 'Vector') -> float: ...
|
|
||||||
def normalize(self) -> 'Vector': ...
|
|
||||||
def dot(self, other: 'Vector') -> float: ...
|
|
||||||
|
|
||||||
class Texture:
|
|
||||||
"""SFML Texture Object for images."""
|
|
||||||
|
|
||||||
def __init__(self, filename: str) -> None: ...
|
|
||||||
|
|
||||||
filename: str
|
|
||||||
width: int
|
|
||||||
height: int
|
|
||||||
sprite_count: int
|
|
||||||
|
|
||||||
class Font:
|
|
||||||
"""SFML Font Object for text rendering."""
|
|
||||||
|
|
||||||
def __init__(self, filename: str) -> None: ...
|
|
||||||
|
|
||||||
filename: str
|
|
||||||
family: str
|
|
||||||
|
|
||||||
class Drawable:
|
class Drawable:
|
||||||
"""Base class for all drawable UI elements."""
|
"""Base class for all drawable UI elements"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
x: float
|
def get_bounds(selfx, y, width, height) -> Any: ...
|
||||||
y: float
|
def move(selfdx, dy) -> Any: ...
|
||||||
visible: bool
|
def resize(selfwidth, height) -> Any: ...
|
||||||
z_index: int
|
|
||||||
name: str
|
|
||||||
pos: Vector
|
|
||||||
|
|
||||||
def get_bounds(self) -> Tuple[float, float, float, float]:
|
class Entity:
|
||||||
"""Get bounding box as (x, y, width, height)."""
|
"""UIEntity objects"""
|
||||||
...
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
def move(self, dx: float, dy: float) -> None:
|
def at(self, *args, **kwargs) -> Any: ...
|
||||||
"""Move by relative offset (dx, dy)."""
|
def die(self, *args, **kwargs) -> Any: ...
|
||||||
...
|
def get_bounds(selfx, y, width, height) -> Any: ...
|
||||||
|
def index(self, *args, **kwargs) -> Any: ...
|
||||||
def resize(self, width: float, height: float) -> None:
|
def move(selfdx, dy) -> Any: ...
|
||||||
"""Resize to new dimensions (width, height)."""
|
def path_to(selfx: int, y: int) -> bool: ...
|
||||||
...
|
def resize(selfwidth, height) -> Any: ...
|
||||||
|
def update_visibility(self) -> None: ...
|
||||||
class Frame(Drawable):
|
|
||||||
"""Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)
|
|
||||||
|
|
||||||
A rectangular frame UI element that can contain other drawable elements.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(self) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self, x: float = 0, y: float = 0, w: float = 0, h: float = 0,
|
|
||||||
fill_color: Optional[Color] = None, outline_color: Optional[Color] = None,
|
|
||||||
outline: float = 0, click: Optional[Callable] = None,
|
|
||||||
children: Optional[List[UIElement]] = None) -> None: ...
|
|
||||||
|
|
||||||
w: float
|
|
||||||
h: float
|
|
||||||
fill_color: Color
|
|
||||||
outline_color: Color
|
|
||||||
outline: float
|
|
||||||
click: Optional[Callable[[float, float, int], None]]
|
|
||||||
children: 'UICollection'
|
|
||||||
clip_children: bool
|
|
||||||
|
|
||||||
class Caption(Drawable):
|
|
||||||
"""Caption(text='', x=0, y=0, font=None, fill_color=None, outline_color=None, outline=0, click=None)
|
|
||||||
|
|
||||||
A text display UI element with customizable font and styling.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(self) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self, text: str = '', x: float = 0, y: float = 0,
|
|
||||||
font: Optional[Font] = None, fill_color: Optional[Color] = None,
|
|
||||||
outline_color: Optional[Color] = None, outline: float = 0,
|
|
||||||
click: Optional[Callable] = None) -> None: ...
|
|
||||||
|
|
||||||
text: str
|
|
||||||
font: Font
|
|
||||||
fill_color: Color
|
|
||||||
outline_color: Color
|
|
||||||
outline: float
|
|
||||||
click: Optional[Callable[[float, float, int], None]]
|
|
||||||
w: float # Read-only, computed from text
|
|
||||||
h: float # Read-only, computed from text
|
|
||||||
|
|
||||||
class Sprite(Drawable):
|
|
||||||
"""Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)
|
|
||||||
|
|
||||||
A sprite UI element that displays a texture or portion of a texture atlas.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(self) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self, x: float = 0, y: float = 0, texture: Optional[Texture] = None,
|
|
||||||
sprite_index: int = 0, scale: float = 1.0,
|
|
||||||
click: Optional[Callable] = None) -> None: ...
|
|
||||||
|
|
||||||
texture: Texture
|
|
||||||
sprite_index: int
|
|
||||||
scale: float
|
|
||||||
click: Optional[Callable[[float, float, int], None]]
|
|
||||||
w: float # Read-only, computed from texture
|
|
||||||
h: float # Read-only, computed from texture
|
|
||||||
|
|
||||||
class Grid(Drawable):
|
|
||||||
"""Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)
|
|
||||||
|
|
||||||
A grid-based tilemap UI element for rendering tile-based levels and game worlds.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(self) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self, x: float = 0, y: float = 0, grid_size: Tuple[int, int] = (20, 20),
|
|
||||||
texture: Optional[Texture] = None, tile_width: int = 16, tile_height: int = 16,
|
|
||||||
scale: float = 1.0, click: Optional[Callable] = None) -> None: ...
|
|
||||||
|
|
||||||
grid_size: Tuple[int, int]
|
|
||||||
tile_width: int
|
|
||||||
tile_height: int
|
|
||||||
texture: Texture
|
|
||||||
scale: float
|
|
||||||
points: List[List['GridPoint']]
|
|
||||||
entities: 'EntityCollection'
|
|
||||||
background_color: Color
|
|
||||||
click: Optional[Callable[[int, int, int], None]]
|
|
||||||
|
|
||||||
def at(self, x: int, y: int) -> 'GridPoint':
|
|
||||||
"""Get grid point at tile coordinates."""
|
|
||||||
...
|
|
||||||
|
|
||||||
class GridPoint:
|
|
||||||
"""Grid point representing a single tile."""
|
|
||||||
|
|
||||||
texture_index: int
|
|
||||||
solid: bool
|
|
||||||
color: Color
|
|
||||||
|
|
||||||
class GridPointState:
|
|
||||||
"""State information for a grid point."""
|
|
||||||
|
|
||||||
texture_index: int
|
|
||||||
color: Color
|
|
||||||
|
|
||||||
class Entity(Drawable):
|
|
||||||
"""Entity(grid_x=0, grid_y=0, texture=None, sprite_index=0, name='')
|
|
||||||
|
|
||||||
Game entity that lives within a Grid.
|
|
||||||
"""
|
|
||||||
|
|
||||||
@overload
|
|
||||||
def __init__(self) -> None: ...
|
|
||||||
@overload
|
|
||||||
def __init__(self, grid_x: float = 0, grid_y: float = 0, texture: Optional[Texture] = None,
|
|
||||||
sprite_index: int = 0, name: str = '') -> None: ...
|
|
||||||
|
|
||||||
grid_x: float
|
|
||||||
grid_y: float
|
|
||||||
texture: Texture
|
|
||||||
sprite_index: int
|
|
||||||
grid: Optional[Grid]
|
|
||||||
|
|
||||||
def at(self, grid_x: float, grid_y: float) -> None:
|
|
||||||
"""Move entity to grid position."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def die(self) -> None:
|
|
||||||
"""Remove entity from its grid."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def index(self) -> int:
|
|
||||||
"""Get index in parent grid's entity collection."""
|
|
||||||
...
|
|
||||||
|
|
||||||
class UICollection:
|
|
||||||
"""Collection of UI drawable elements (Frame, Caption, Sprite, Grid)."""
|
|
||||||
|
|
||||||
def __len__(self) -> int: ...
|
|
||||||
def __getitem__(self, index: int) -> UIElement: ...
|
|
||||||
def __setitem__(self, index: int, value: UIElement) -> None: ...
|
|
||||||
def __delitem__(self, index: int) -> None: ...
|
|
||||||
def __contains__(self, item: UIElement) -> bool: ...
|
|
||||||
def __iter__(self) -> Any: ...
|
|
||||||
def __add__(self, other: 'UICollection') -> 'UICollection': ...
|
|
||||||
def __iadd__(self, other: 'UICollection') -> 'UICollection': ...
|
|
||||||
|
|
||||||
def append(self, item: UIElement) -> None: ...
|
|
||||||
def extend(self, items: List[UIElement]) -> None: ...
|
|
||||||
def remove(self, item: UIElement) -> None: ...
|
|
||||||
def index(self, item: UIElement) -> int: ...
|
|
||||||
def count(self, item: UIElement) -> int: ...
|
|
||||||
|
|
||||||
class EntityCollection:
|
class EntityCollection:
|
||||||
"""Collection of Entity objects."""
|
"""Iterable, indexable collection of Entities"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
def __len__(self) -> int: ...
|
def append(self, *args, **kwargs) -> Any: ...
|
||||||
def __getitem__(self, index: int) -> Entity: ...
|
def count(self, *args, **kwargs) -> Any: ...
|
||||||
def __setitem__(self, index: int, value: Entity) -> None: ...
|
def extend(self, *args, **kwargs) -> Any: ...
|
||||||
def __delitem__(self, index: int) -> None: ...
|
def index(self, *args, **kwargs) -> Any: ...
|
||||||
def __contains__(self, item: Entity) -> bool: ...
|
def remove(self, *args, **kwargs) -> Any: ...
|
||||||
def __iter__(self) -> Any: ...
|
|
||||||
def __add__(self, other: 'EntityCollection') -> 'EntityCollection': ...
|
|
||||||
def __iadd__(self, other: 'EntityCollection') -> 'EntityCollection': ...
|
|
||||||
|
|
||||||
def append(self, item: Entity) -> None: ...
|
class Font:
|
||||||
def extend(self, items: List[Entity]) -> None: ...
|
"""SFML Font Object"""
|
||||||
def remove(self, item: Entity) -> None: ...
|
def __init__(selftype(self)) -> None: ...
|
||||||
def index(self, item: Entity) -> int: ...
|
|
||||||
def count(self, item: Entity) -> int: ...
|
class Frame:
|
||||||
|
"""Frame(x=0, y=0, w=0, h=0, fill_color=None, outline_color=None, outline=0, click=None, children=None)"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
|
def get_bounds(selfx, y, width, height) -> Any: ...
|
||||||
|
def move(selfdx, dy) -> Any: ...
|
||||||
|
def resize(selfwidth, height) -> Any: ...
|
||||||
|
|
||||||
|
class Grid:
|
||||||
|
"""Grid(x=0, y=0, grid_size=(20, 20), texture=None, tile_width=16, tile_height=16, scale=1.0, click=None)"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
|
def at(self, *args, **kwargs) -> Any: ...
|
||||||
|
def compute_astar_path(selfx1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]: ...
|
||||||
|
def compute_dijkstra(selfroot_x: int, root_y: int, diagonal_cost: float = 1.41) -> None: ...
|
||||||
|
def compute_fov(selfx: int, y: int, radius: int = 0, light_walls: bool = True, algorithm: int = FOV_BASIC) -> None: ...
|
||||||
|
def find_path(selfx1: int, y1: int, x2: int, y2: int, diagonal_cost: float = 1.41) -> List[Tuple[int, int]]: ...
|
||||||
|
def get_bounds(selfx, y, width, height) -> Any: ...
|
||||||
|
def get_dijkstra_distance(selfx: int, y: int) -> Optional[float]: ...
|
||||||
|
def get_dijkstra_path(selfx: int, y: int) -> List[Tuple[int, int]]: ...
|
||||||
|
def is_in_fov(selfx: int, y: int) -> bool: ...
|
||||||
|
def move(selfdx, dy) -> Any: ...
|
||||||
|
def resize(selfwidth, height) -> Any: ...
|
||||||
|
|
||||||
|
class GridPoint:
|
||||||
|
"""UIGridPoint object"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
|
class GridPointState:
|
||||||
|
"""UIGridPointState object"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
class Scene:
|
class Scene:
|
||||||
"""Base class for object-oriented scenes."""
|
"""Base class for object-oriented scenes"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
name: str
|
def activate(self, *args, **kwargs) -> Any: ...
|
||||||
|
def get_ui(self, *args, **kwargs) -> Any: ...
|
||||||
|
def register_keyboard(selfalternative to overriding on_keypress) -> Any: ...
|
||||||
|
|
||||||
def __init__(self, name: str) -> None: ...
|
class Sprite:
|
||||||
|
"""Sprite(x=0, y=0, texture=None, sprite_index=0, scale=1.0, click=None)"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
def activate(self) -> None:
|
def get_bounds(selfx, y, width, height) -> Any: ...
|
||||||
"""Called when scene becomes active."""
|
def move(selfdx, dy) -> Any: ...
|
||||||
...
|
def resize(selfwidth, height) -> Any: ...
|
||||||
|
|
||||||
def deactivate(self) -> None:
|
class Texture:
|
||||||
"""Called when scene becomes inactive."""
|
"""SFML Texture Object"""
|
||||||
...
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
def get_ui(self) -> UICollection:
|
|
||||||
"""Get UI elements collection."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def on_keypress(self, key: str, pressed: bool) -> None:
|
|
||||||
"""Handle keyboard events."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def on_click(self, x: float, y: float, button: int) -> None:
|
|
||||||
"""Handle mouse clicks."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def on_enter(self) -> None:
|
|
||||||
"""Called when entering the scene."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def on_exit(self) -> None:
|
|
||||||
"""Called when leaving the scene."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def on_resize(self, width: int, height: int) -> None:
|
|
||||||
"""Handle window resize events."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def update(self, dt: float) -> None:
|
|
||||||
"""Update scene logic."""
|
|
||||||
...
|
|
||||||
|
|
||||||
class Timer:
|
class Timer:
|
||||||
"""Timer object for scheduled callbacks."""
|
"""Timer object for scheduled callbacks"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
name: str
|
def cancel(self, *args, **kwargs) -> Any: ...
|
||||||
interval: int
|
def pause(self, *args, **kwargs) -> Any: ...
|
||||||
active: bool
|
def restart(self, *args, **kwargs) -> Any: ...
|
||||||
|
def resume(self, *args, **kwargs) -> Any: ...
|
||||||
|
|
||||||
def __init__(self, name: str, callback: Callable[[float], None], interval: int) -> None: ...
|
class UICollection:
|
||||||
|
"""Iterable, indexable collection of UI objects"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
def pause(self) -> None:
|
def append(self, *args, **kwargs) -> Any: ...
|
||||||
"""Pause the timer."""
|
def count(self, *args, **kwargs) -> Any: ...
|
||||||
...
|
def extend(self, *args, **kwargs) -> Any: ...
|
||||||
|
def index(self, *args, **kwargs) -> Any: ...
|
||||||
|
def remove(self, *args, **kwargs) -> Any: ...
|
||||||
|
|
||||||
def resume(self) -> None:
|
class UICollectionIter:
|
||||||
"""Resume the timer."""
|
"""Iterator for a collection of UI objects"""
|
||||||
...
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
def cancel(self) -> None:
|
class UIEntityCollectionIter:
|
||||||
"""Cancel and remove the timer."""
|
"""Iterator for a collection of UI objects"""
|
||||||
...
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
|
class Vector:
|
||||||
|
"""SFML Vector Object"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
|
def angle(self, *args, **kwargs) -> Any: ...
|
||||||
|
def copy(self, *args, **kwargs) -> Any: ...
|
||||||
|
def distance_to(self, *args, **kwargs) -> Any: ...
|
||||||
|
def dot(self, *args, **kwargs) -> Any: ...
|
||||||
|
def magnitude(self, *args, **kwargs) -> Any: ...
|
||||||
|
def magnitude_squared(self, *args, **kwargs) -> Any: ...
|
||||||
|
def normalize(self, *args, **kwargs) -> Any: ...
|
||||||
|
|
||||||
class Window:
|
class Window:
|
||||||
"""Window singleton for managing the game window."""
|
"""Window singleton for accessing and modifying the game window properties"""
|
||||||
|
def __init__(selftype(self)) -> None: ...
|
||||||
|
|
||||||
resolution: Tuple[int, int]
|
def center(self, *args, **kwargs) -> Any: ...
|
||||||
fullscreen: bool
|
def get(self, *args, **kwargs) -> Any: ...
|
||||||
vsync: bool
|
def screenshot(self, *args, **kwargs) -> Any: ...
|
||||||
title: str
|
|
||||||
fps_limit: int
|
|
||||||
game_resolution: Tuple[int, int]
|
|
||||||
scaling_mode: str
|
|
||||||
|
|
||||||
@staticmethod
|
# Functions
|
||||||
def get() -> 'Window':
|
|
||||||
"""Get the window singleton instance."""
|
|
||||||
...
|
|
||||||
|
|
||||||
class Animation:
|
def createScene(name: str) -> None: ...
|
||||||
"""Animation object for animating UI properties."""
|
def createSoundBuffer(filename: str) -> int: ...
|
||||||
|
def currentScene() -> str: ...
|
||||||
|
def delTimer(name: str) -> None: ...
|
||||||
|
def exit() -> None: ...
|
||||||
|
def find(name: str, scene: str = None) -> UIDrawable | None: ...
|
||||||
|
def findAll(pattern: str, scene: str = None) -> list: ...
|
||||||
|
def getMetrics() -> dict: ...
|
||||||
|
def getMusicVolume() -> int: ...
|
||||||
|
def getSoundVolume() -> int: ...
|
||||||
|
def keypressScene(handler: callable) -> None: ...
|
||||||
|
def loadMusic(filename: str) -> None: ...
|
||||||
|
def playSound(buffer_id: int) -> None: ...
|
||||||
|
def sceneUI(scene: str = None) -> list: ...
|
||||||
|
def setMusicVolume(volume: int) -> None: ...
|
||||||
|
def setScale(multiplier: float) -> None: ...
|
||||||
|
def setScene(scene: str, transition: str = None, duration: float = 0.0) -> None: ...
|
||||||
|
def setSoundVolume(volume: int) -> None: ...
|
||||||
|
def setTimer(name: str, handler: callable, interval: int) -> None: ...
|
||||||
|
|
||||||
target: Any
|
# Constants
|
||||||
property: str
|
|
||||||
duration: float
|
|
||||||
easing: str
|
|
||||||
loop: bool
|
|
||||||
on_complete: Optional[Callable]
|
|
||||||
|
|
||||||
def __init__(self, target: Any, property: str, start_value: Any, end_value: Any,
|
FOV_BASIC: int
|
||||||
duration: float, easing: str = 'linear', loop: bool = False,
|
FOV_DIAMOND: int
|
||||||
on_complete: Optional[Callable] = None) -> None: ...
|
FOV_PERMISSIVE_0: int
|
||||||
|
FOV_PERMISSIVE_1: int
|
||||||
def start(self) -> None:
|
FOV_PERMISSIVE_2: int
|
||||||
"""Start the animation."""
|
FOV_PERMISSIVE_3: int
|
||||||
...
|
FOV_PERMISSIVE_4: int
|
||||||
|
FOV_PERMISSIVE_5: int
|
||||||
def update(self, dt: float) -> bool:
|
FOV_PERMISSIVE_6: int
|
||||||
"""Update animation, returns True if still running."""
|
FOV_PERMISSIVE_7: int
|
||||||
...
|
FOV_PERMISSIVE_8: int
|
||||||
|
FOV_RESTRICTIVE: int
|
||||||
def get_current_value(self) -> Any:
|
FOV_SHADOW: int
|
||||||
"""Get the current interpolated value."""
|
default_font: Any
|
||||||
...
|
default_texture: Any
|
||||||
|
|
||||||
# Module functions
|
|
||||||
|
|
||||||
def createSoundBuffer(filename: str) -> int:
|
|
||||||
"""Load a sound effect from a file and return its buffer ID."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def loadMusic(filename: str) -> None:
|
|
||||||
"""Load and immediately play background music from a file."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def setMusicVolume(volume: int) -> None:
|
|
||||||
"""Set the global music volume (0-100)."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def setSoundVolume(volume: int) -> None:
|
|
||||||
"""Set the global sound effects volume (0-100)."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def playSound(buffer_id: int) -> None:
|
|
||||||
"""Play a sound effect using a previously loaded buffer."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def getMusicVolume() -> int:
|
|
||||||
"""Get the current music volume level (0-100)."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def getSoundVolume() -> int:
|
|
||||||
"""Get the current sound effects volume level (0-100)."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def sceneUI(scene: Optional[str] = None) -> UICollection:
|
|
||||||
"""Get all UI elements for a scene."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def currentScene() -> str:
|
|
||||||
"""Get the name of the currently active scene."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def setScene(scene: str, transition: Optional[str] = None, duration: float = 0.0) -> None:
|
|
||||||
"""Switch to a different scene with optional transition effect."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def createScene(name: str) -> None:
|
|
||||||
"""Create a new empty scene."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def keypressScene(handler: Callable[[str, bool], None]) -> None:
|
|
||||||
"""Set the keyboard event handler for the current scene."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def setTimer(name: str, handler: Callable[[float], None], interval: int) -> None:
|
|
||||||
"""Create or update a recurring timer."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def delTimer(name: str) -> None:
|
|
||||||
"""Stop and remove a timer."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def exit() -> None:
|
|
||||||
"""Cleanly shut down the game engine and exit the application."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def setScale(multiplier: float) -> None:
|
|
||||||
"""Scale the game window size (deprecated - use Window.resolution)."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def find(name: str, scene: Optional[str] = None) -> Optional[UIElement]:
|
|
||||||
"""Find the first UI element with the specified name."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def findAll(pattern: str, scene: Optional[str] = None) -> List[UIElement]:
|
|
||||||
"""Find all UI elements matching a name pattern (supports * wildcards)."""
|
|
||||||
...
|
|
||||||
|
|
||||||
def getMetrics() -> Dict[str, Union[int, float]]:
|
|
||||||
"""Get current performance metrics."""
|
|
||||||
...
|
|
||||||
|
|
||||||
# Submodule
|
|
||||||
class automation:
|
|
||||||
"""Automation API for testing and scripting."""
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def screenshot(filename: str) -> bool:
|
|
||||||
"""Save a screenshot to the specified file."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def position() -> Tuple[int, int]:
|
|
||||||
"""Get current mouse position as (x, y) tuple."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def size() -> Tuple[int, int]:
|
|
||||||
"""Get screen size as (width, height) tuple."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def onScreen(x: int, y: int) -> bool:
|
|
||||||
"""Check if coordinates are within screen bounds."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def moveTo(x: int, y: int, duration: float = 0.0) -> None:
|
|
||||||
"""Move mouse to absolute position."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def moveRel(xOffset: int, yOffset: int, duration: float = 0.0) -> None:
|
|
||||||
"""Move mouse relative to current position."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def dragTo(x: int, y: int, duration: float = 0.0, button: str = 'left') -> None:
|
|
||||||
"""Drag mouse to position."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def dragRel(xOffset: int, yOffset: int, duration: float = 0.0, button: str = 'left') -> None:
|
|
||||||
"""Drag mouse relative to current position."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def click(x: Optional[int] = None, y: Optional[int] = None, clicks: int = 1,
|
|
||||||
interval: float = 0.0, button: str = 'left') -> None:
|
|
||||||
"""Click mouse at position."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mouseDown(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None:
|
|
||||||
"""Press mouse button down."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def mouseUp(x: Optional[int] = None, y: Optional[int] = None, button: str = 'left') -> None:
|
|
||||||
"""Release mouse button."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def keyDown(key: str) -> None:
|
|
||||||
"""Press key down."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def keyUp(key: str) -> None:
|
|
||||||
"""Release key."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def press(key: str) -> None:
|
|
||||||
"""Press and release a key."""
|
|
||||||
...
|
|
||||||
|
|
||||||
@staticmethod
|
|
||||||
def typewrite(text: str, interval: float = 0.0) -> None:
|
|
||||||
"""Type text with optional interval between characters."""
|
|
||||||
...
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "UIEntity.h"
|
#include "UIEntity.h"
|
||||||
#include "PyAnimation.h"
|
#include "PyAnimation.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include <unordered_map>
|
#include <unordered_map>
|
||||||
|
@ -47,11 +46,6 @@ Animation::~Animation() {
|
||||||
Py_DECREF(callback);
|
Py_DECREF(callback);
|
||||||
PyGILState_Release(gstate);
|
PyGILState_Release(gstate);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Clean up cache entry
|
|
||||||
if (serial_number != 0) {
|
|
||||||
PythonObjectCache::getInstance().remove(serial_number);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::start(std::shared_ptr<UIDrawable> target) {
|
void Animation::start(std::shared_ptr<UIDrawable> target) {
|
||||||
|
|
|
@ -90,9 +90,6 @@ private:
|
||||||
bool callbackTriggered = false; // Ensure callback only fires once
|
bool callbackTriggered = false; // Ensure callback only fires once
|
||||||
PyObject* pyAnimationWrapper = nullptr; // Weak reference to PyAnimation if created from Python
|
PyObject* pyAnimationWrapper = nullptr; // Weak reference to PyAnimation if created from Python
|
||||||
|
|
||||||
// Python object cache support
|
|
||||||
uint64_t serial_number = 0;
|
|
||||||
|
|
||||||
// Helper to interpolate between values
|
// Helper to interpolate between values
|
||||||
AnimationValue interpolate(float t) const;
|
AnimationValue interpolate(float t) const;
|
||||||
|
|
||||||
|
|
|
@ -5,7 +5,6 @@
|
||||||
#include "UITestScene.h"
|
#include "UITestScene.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "Animation.h"
|
#include "Animation.h"
|
||||||
#include "Timer.h"
|
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
GameEngine::GameEngine() : GameEngine(McRogueFaceConfig{})
|
||||||
|
@ -276,7 +275,7 @@ void GameEngine::run()
|
||||||
cleanup();
|
cleanup();
|
||||||
}
|
}
|
||||||
|
|
||||||
std::shared_ptr<Timer> GameEngine::getTimer(const std::string& name)
|
std::shared_ptr<PyTimerCallable> GameEngine::getTimer(const std::string& name)
|
||||||
{
|
{
|
||||||
auto it = timers.find(name);
|
auto it = timers.find(name);
|
||||||
if (it != timers.end()) {
|
if (it != timers.end()) {
|
||||||
|
@ -294,7 +293,7 @@ void GameEngine::manageTimer(std::string name, PyObject* target, int interval)
|
||||||
{
|
{
|
||||||
// Delete: Overwrite existing timer with one that calls None. This will be deleted in the next timer check
|
// Delete: Overwrite existing timer with one that calls None. This will be deleted in the next timer check
|
||||||
// see gitea issue #4: this allows for a timer to be deleted during its own call to itself
|
// see gitea issue #4: this allows for a timer to be deleted during its own call to itself
|
||||||
timers[name] = std::make_shared<Timer>(Py_None, 1000, runtime.getElapsedTime().asMilliseconds());
|
timers[name] = std::make_shared<PyTimerCallable>(Py_None, 1000, runtime.getElapsedTime().asMilliseconds());
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -303,7 +302,7 @@ void GameEngine::manageTimer(std::string name, PyObject* target, int interval)
|
||||||
std::cout << "Refusing to initialize timer to None. It's not an error, it's just pointless." << std::endl;
|
std::cout << "Refusing to initialize timer to None. It's not an error, it's just pointless." << std::endl;
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
timers[name] = std::make_shared<Timer>(target, interval, runtime.getElapsedTime().asMilliseconds());
|
timers[name] = std::make_shared<PyTimerCallable>(target, interval, runtime.getElapsedTime().asMilliseconds());
|
||||||
}
|
}
|
||||||
|
|
||||||
void GameEngine::testTimers()
|
void GameEngine::testTimers()
|
||||||
|
@ -314,8 +313,7 @@ void GameEngine::testTimers()
|
||||||
{
|
{
|
||||||
it->second->test(now);
|
it->second->test(now);
|
||||||
|
|
||||||
// Remove timers that have been cancelled or are one-shot and fired
|
if (it->second->isNone())
|
||||||
if (!it->second->getCallback() || it->second->getCallback() == Py_None)
|
|
||||||
{
|
{
|
||||||
it = timers.erase(it);
|
it = timers.erase(it);
|
||||||
}
|
}
|
||||||
|
|
|
@ -58,7 +58,8 @@ private:
|
||||||
|
|
||||||
public:
|
public:
|
||||||
sf::Clock runtime;
|
sf::Clock runtime;
|
||||||
std::map<std::string, std::shared_ptr<Timer>> timers;
|
//std::map<std::string, Timer> timers;
|
||||||
|
std::map<std::string, std::shared_ptr<PyTimerCallable>> timers;
|
||||||
std::string scene;
|
std::string scene;
|
||||||
|
|
||||||
// Profiling metrics
|
// Profiling metrics
|
||||||
|
@ -115,7 +116,7 @@ public:
|
||||||
float getFrameTime() { return frameTime; }
|
float getFrameTime() { return frameTime; }
|
||||||
sf::View getView() { return visible; }
|
sf::View getView() { return visible; }
|
||||||
void manageTimer(std::string, PyObject*, int);
|
void manageTimer(std::string, PyObject*, int);
|
||||||
std::shared_ptr<Timer> getTimer(const std::string& name);
|
std::shared_ptr<PyTimerCallable> getTimer(const std::string& name);
|
||||||
void setWindowScale(float);
|
void setWindowScale(float);
|
||||||
bool isHeadless() const { return headless; }
|
bool isHeadless() const { return headless; }
|
||||||
void processEvent(const sf::Event& event);
|
void processEvent(const sf::Event& event);
|
||||||
|
|
|
@ -267,14 +267,6 @@ PyObject* PyInit_mcrfpy()
|
||||||
PySceneType.tp_methods = PySceneClass::methods;
|
PySceneType.tp_methods = PySceneClass::methods;
|
||||||
PySceneType.tp_getset = PySceneClass::getsetters;
|
PySceneType.tp_getset = PySceneClass::getsetters;
|
||||||
|
|
||||||
// Set up weakref support for all types that need it
|
|
||||||
PyTimerType.tp_weaklistoffset = offsetof(PyTimerObject, weakreflist);
|
|
||||||
PyUIFrameType.tp_weaklistoffset = offsetof(PyUIFrameObject, weakreflist);
|
|
||||||
PyUICaptionType.tp_weaklistoffset = offsetof(PyUICaptionObject, weakreflist);
|
|
||||||
PyUISpriteType.tp_weaklistoffset = offsetof(PyUISpriteObject, weakreflist);
|
|
||||||
PyUIGridType.tp_weaklistoffset = offsetof(PyUIGridObject, weakreflist);
|
|
||||||
PyUIEntityType.tp_weaklistoffset = offsetof(PyUIEntityObject, weakreflist);
|
|
||||||
|
|
||||||
int i = 0;
|
int i = 0;
|
||||||
auto t = pytypes[i];
|
auto t = pytypes[i];
|
||||||
while (t != nullptr)
|
while (t != nullptr)
|
||||||
|
|
|
@ -5,21 +5,6 @@ PyCallable::PyCallable(PyObject* _target)
|
||||||
target = Py_XNewRef(_target);
|
target = Py_XNewRef(_target);
|
||||||
}
|
}
|
||||||
|
|
||||||
PyCallable::PyCallable(const PyCallable& other)
|
|
||||||
{
|
|
||||||
target = Py_XNewRef(other.target);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyCallable& PyCallable::operator=(const PyCallable& other)
|
|
||||||
{
|
|
||||||
if (this != &other) {
|
|
||||||
PyObject* old_target = target;
|
|
||||||
target = Py_XNewRef(other.target);
|
|
||||||
Py_XDECREF(old_target);
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyCallable::~PyCallable()
|
PyCallable::~PyCallable()
|
||||||
{
|
{
|
||||||
if (target)
|
if (target)
|
||||||
|
@ -36,6 +21,103 @@ bool PyCallable::isNone() const
|
||||||
return (target == Py_None || target == NULL);
|
return (target == Py_None || target == NULL);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
PyTimerCallable::PyTimerCallable(PyObject* _target, int _interval, int now)
|
||||||
|
: PyCallable(_target), interval(_interval), last_ran(now),
|
||||||
|
paused(false), pause_start_time(0), total_paused_time(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
PyTimerCallable::PyTimerCallable()
|
||||||
|
: PyCallable(Py_None), interval(0), last_ran(0),
|
||||||
|
paused(false), pause_start_time(0), total_paused_time(0)
|
||||||
|
{}
|
||||||
|
|
||||||
|
bool PyTimerCallable::hasElapsed(int now)
|
||||||
|
{
|
||||||
|
if (paused) return false;
|
||||||
|
return now >= last_ran + interval;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyTimerCallable::call(int now)
|
||||||
|
{
|
||||||
|
PyObject* args = Py_BuildValue("(i)", now);
|
||||||
|
PyObject* retval = PyCallable::call(args, NULL);
|
||||||
|
if (!retval)
|
||||||
|
{
|
||||||
|
PyErr_Print();
|
||||||
|
PyErr_Clear();
|
||||||
|
} else if (retval != Py_None)
|
||||||
|
{
|
||||||
|
std::cout << "timer returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
|
||||||
|
std::cout << PyUnicode_AsUTF8(PyObject_Repr(retval)) << std::endl;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool PyTimerCallable::test(int now)
|
||||||
|
{
|
||||||
|
if(hasElapsed(now))
|
||||||
|
{
|
||||||
|
call(now);
|
||||||
|
last_ran = now;
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyTimerCallable::pause(int current_time)
|
||||||
|
{
|
||||||
|
if (!paused) {
|
||||||
|
paused = true;
|
||||||
|
pause_start_time = current_time;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyTimerCallable::resume(int current_time)
|
||||||
|
{
|
||||||
|
if (paused) {
|
||||||
|
paused = false;
|
||||||
|
int paused_duration = current_time - pause_start_time;
|
||||||
|
total_paused_time += paused_duration;
|
||||||
|
// Adjust last_ran to account for the pause
|
||||||
|
last_ran += paused_duration;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyTimerCallable::restart(int current_time)
|
||||||
|
{
|
||||||
|
last_ran = current_time;
|
||||||
|
paused = false;
|
||||||
|
pause_start_time = 0;
|
||||||
|
total_paused_time = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyTimerCallable::cancel()
|
||||||
|
{
|
||||||
|
// Cancel by setting target to None
|
||||||
|
if (target && target != Py_None) {
|
||||||
|
Py_DECREF(target);
|
||||||
|
}
|
||||||
|
target = Py_None;
|
||||||
|
Py_INCREF(Py_None);
|
||||||
|
}
|
||||||
|
|
||||||
|
int PyTimerCallable::getRemaining(int current_time) const
|
||||||
|
{
|
||||||
|
if (paused) {
|
||||||
|
// When paused, calculate time remaining from when it was paused
|
||||||
|
int elapsed_when_paused = pause_start_time - last_ran;
|
||||||
|
return interval - elapsed_when_paused;
|
||||||
|
}
|
||||||
|
int elapsed = current_time - last_ran;
|
||||||
|
return interval - elapsed;
|
||||||
|
}
|
||||||
|
|
||||||
|
void PyTimerCallable::setCallback(PyObject* new_callback)
|
||||||
|
{
|
||||||
|
if (target && target != Py_None) {
|
||||||
|
Py_DECREF(target);
|
||||||
|
}
|
||||||
|
target = Py_XNewRef(new_callback);
|
||||||
|
}
|
||||||
|
|
||||||
PyClickCallable::PyClickCallable(PyObject* _target)
|
PyClickCallable::PyClickCallable(PyObject* _target)
|
||||||
: PyCallable(_target)
|
: PyCallable(_target)
|
||||||
|
|
|
@ -6,15 +6,45 @@ class PyCallable
|
||||||
{
|
{
|
||||||
protected:
|
protected:
|
||||||
PyObject* target;
|
PyObject* target;
|
||||||
|
|
||||||
public:
|
|
||||||
PyCallable(PyObject*);
|
PyCallable(PyObject*);
|
||||||
PyCallable(const PyCallable& other);
|
|
||||||
PyCallable& operator=(const PyCallable& other);
|
|
||||||
~PyCallable();
|
~PyCallable();
|
||||||
PyObject* call(PyObject*, PyObject*);
|
PyObject* call(PyObject*, PyObject*);
|
||||||
|
public:
|
||||||
bool isNone() const;
|
bool isNone() const;
|
||||||
PyObject* borrow() const { return target; }
|
};
|
||||||
|
|
||||||
|
class PyTimerCallable: public PyCallable
|
||||||
|
{
|
||||||
|
private:
|
||||||
|
int interval;
|
||||||
|
int last_ran;
|
||||||
|
void call(int);
|
||||||
|
|
||||||
|
// Pause/resume support
|
||||||
|
bool paused;
|
||||||
|
int pause_start_time;
|
||||||
|
int total_paused_time;
|
||||||
|
|
||||||
|
public:
|
||||||
|
bool hasElapsed(int);
|
||||||
|
bool test(int);
|
||||||
|
PyTimerCallable(PyObject*, int, int);
|
||||||
|
PyTimerCallable();
|
||||||
|
|
||||||
|
// Timer control methods
|
||||||
|
void pause(int current_time);
|
||||||
|
void resume(int current_time);
|
||||||
|
void restart(int current_time);
|
||||||
|
void cancel();
|
||||||
|
|
||||||
|
// Timer state queries
|
||||||
|
bool isPaused() const { return paused; }
|
||||||
|
bool isActive() const { return !isNone() && !paused; }
|
||||||
|
int getInterval() const { return interval; }
|
||||||
|
void setInterval(int new_interval) { interval = new_interval; }
|
||||||
|
int getRemaining(int current_time) const;
|
||||||
|
PyObject* getCallback() { return target; }
|
||||||
|
void setCallback(PyObject* new_callback);
|
||||||
};
|
};
|
||||||
|
|
||||||
class PyClickCallable: public PyCallable
|
class PyClickCallable: public PyCallable
|
||||||
|
@ -24,11 +54,6 @@ public:
|
||||||
PyObject* borrow();
|
PyObject* borrow();
|
||||||
PyClickCallable(PyObject*);
|
PyClickCallable(PyObject*);
|
||||||
PyClickCallable();
|
PyClickCallable();
|
||||||
PyClickCallable(const PyClickCallable& other) : PyCallable(other) {}
|
|
||||||
PyClickCallable& operator=(const PyClickCallable& other) {
|
|
||||||
PyCallable::operator=(other);
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
};
|
};
|
||||||
|
|
||||||
class PyKeyCallable: public PyCallable
|
class PyKeyCallable: public PyCallable
|
||||||
|
|
|
@ -1,8 +1,7 @@
|
||||||
#include "PyTimer.h"
|
#include "PyTimer.h"
|
||||||
#include "Timer.h"
|
#include "PyCallable.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "Resources.h"
|
#include "Resources.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
#include <sstream>
|
#include <sstream>
|
||||||
|
|
||||||
PyObject* PyTimer::repr(PyObject* self) {
|
PyObject* PyTimer::repr(PyObject* self) {
|
||||||
|
@ -12,22 +11,7 @@ PyObject* PyTimer::repr(PyObject* self) {
|
||||||
|
|
||||||
if (timer->data) {
|
if (timer->data) {
|
||||||
oss << "interval=" << timer->data->getInterval() << "ms ";
|
oss << "interval=" << timer->data->getInterval() << "ms ";
|
||||||
if (timer->data->isOnce()) {
|
oss << (timer->data->isPaused() ? "paused" : "active");
|
||||||
oss << "once=True ";
|
|
||||||
}
|
|
||||||
if (timer->data->isPaused()) {
|
|
||||||
oss << "paused";
|
|
||||||
// Get current time to show remaining
|
|
||||||
int current_time = 0;
|
|
||||||
if (Resources::game) {
|
|
||||||
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
|
||||||
}
|
|
||||||
oss << " (remaining=" << timer->data->getRemaining(current_time) << "ms)";
|
|
||||||
} else if (timer->data->isActive()) {
|
|
||||||
oss << "active";
|
|
||||||
} else {
|
|
||||||
oss << "cancelled";
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
oss << "uninitialized";
|
oss << "uninitialized";
|
||||||
}
|
}
|
||||||
|
@ -41,20 +25,18 @@ PyObject* PyTimer::pynew(PyTypeObject* type, PyObject* args, PyObject* kwds) {
|
||||||
if (self) {
|
if (self) {
|
||||||
new(&self->name) std::string(); // Placement new for std::string
|
new(&self->name) std::string(); // Placement new for std::string
|
||||||
self->data = nullptr;
|
self->data = nullptr;
|
||||||
self->weakreflist = nullptr; // Initialize weakref list
|
|
||||||
}
|
}
|
||||||
return (PyObject*)self;
|
return (PyObject*)self;
|
||||||
}
|
}
|
||||||
|
|
||||||
int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
||||||
static const char* kwlist[] = {"name", "callback", "interval", "once", NULL};
|
static const char* kwlist[] = {"name", "callback", "interval", NULL};
|
||||||
const char* name = nullptr;
|
const char* name = nullptr;
|
||||||
PyObject* callback = nullptr;
|
PyObject* callback = nullptr;
|
||||||
int interval = 0;
|
int interval = 0;
|
||||||
int once = 0; // Use int for bool parameter
|
|
||||||
|
|
||||||
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOi|p", const_cast<char**>(kwlist),
|
if (!PyArg_ParseTupleAndKeywords(args, kwds, "sOi", const_cast<char**>(kwlist),
|
||||||
&name, &callback, &interval, &once)) {
|
&name, &callback, &interval)) {
|
||||||
return -1;
|
return -1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,18 +58,8 @@ int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
||||||
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
current_time = Resources::game->runtime.getElapsedTime().asMilliseconds();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Create the timer
|
// Create the timer callable
|
||||||
self->data = std::make_shared<Timer>(callback, interval, current_time, (bool)once);
|
self->data = std::make_shared<PyTimerCallable>(callback, interval, current_time);
|
||||||
|
|
||||||
// Register in Python object cache
|
|
||||||
if (self->data->serial_number == 0) {
|
|
||||||
self->data->serial_number = PythonObjectCache::getInstance().assignSerial();
|
|
||||||
PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL);
|
|
||||||
if (weakref) {
|
|
||||||
PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref);
|
|
||||||
Py_DECREF(weakref); // Cache owns the reference now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Register with game engine
|
// Register with game engine
|
||||||
if (Resources::game) {
|
if (Resources::game) {
|
||||||
|
@ -98,11 +70,6 @@ int PyTimer::init(PyTimerObject* self, PyObject* args, PyObject* kwds) {
|
||||||
}
|
}
|
||||||
|
|
||||||
void PyTimer::dealloc(PyTimerObject* self) {
|
void PyTimer::dealloc(PyTimerObject* self) {
|
||||||
// Clear weakrefs first
|
|
||||||
if (self->weakreflist != nullptr) {
|
|
||||||
PyObject_ClearWeakRefs((PyObject*)self);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Remove from game engine if still registered
|
// Remove from game engine if still registered
|
||||||
if (Resources::game && !self->name.empty()) {
|
if (Resources::game && !self->name.empty()) {
|
||||||
auto it = Resources::game->timers.find(self->name);
|
auto it = Resources::game->timers.find(self->name);
|
||||||
|
@ -277,37 +244,7 @@ int PyTimer::set_callback(PyTimerObject* self, PyObject* value, void* closure) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
PyObject* PyTimer::get_once(PyTimerObject* self, void* closure) {
|
|
||||||
if (!self->data) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
return PyBool_FromLong(self->data->isOnce());
|
|
||||||
}
|
|
||||||
|
|
||||||
int PyTimer::set_once(PyTimerObject* self, PyObject* value, void* closure) {
|
|
||||||
if (!self->data) {
|
|
||||||
PyErr_SetString(PyExc_RuntimeError, "Timer not initialized");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (!PyBool_Check(value)) {
|
|
||||||
PyErr_SetString(PyExc_TypeError, "once must be a boolean");
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
|
|
||||||
self->data->setOnce(PyObject_IsTrue(value));
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* PyTimer::get_name(PyTimerObject* self, void* closure) {
|
|
||||||
return PyUnicode_FromString(self->name.c_str());
|
|
||||||
}
|
|
||||||
|
|
||||||
PyGetSetDef PyTimer::getsetters[] = {
|
PyGetSetDef PyTimer::getsetters[] = {
|
||||||
{"name", (getter)PyTimer::get_name, NULL,
|
|
||||||
"Timer name (read-only)", NULL},
|
|
||||||
{"interval", (getter)PyTimer::get_interval, (setter)PyTimer::set_interval,
|
{"interval", (getter)PyTimer::get_interval, (setter)PyTimer::set_interval,
|
||||||
"Timer interval in milliseconds", NULL},
|
"Timer interval in milliseconds", NULL},
|
||||||
{"remaining", (getter)PyTimer::get_remaining, NULL,
|
{"remaining", (getter)PyTimer::get_remaining, NULL,
|
||||||
|
@ -318,27 +255,17 @@ PyGetSetDef PyTimer::getsetters[] = {
|
||||||
"Whether the timer is active and not paused", NULL},
|
"Whether the timer is active and not paused", NULL},
|
||||||
{"callback", (getter)PyTimer::get_callback, (setter)PyTimer::set_callback,
|
{"callback", (getter)PyTimer::get_callback, (setter)PyTimer::set_callback,
|
||||||
"The callback function to be called", NULL},
|
"The callback function to be called", NULL},
|
||||||
{"once", (getter)PyTimer::get_once, (setter)PyTimer::set_once,
|
|
||||||
"Whether the timer stops after firing once", NULL},
|
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
||||||
|
|
||||||
PyMethodDef PyTimer::methods[] = {
|
PyMethodDef PyTimer::methods[] = {
|
||||||
{"pause", (PyCFunction)PyTimer::pause, METH_NOARGS,
|
{"pause", (PyCFunction)PyTimer::pause, METH_NOARGS,
|
||||||
"pause() -> None\n\n"
|
"Pause the timer"},
|
||||||
"Pause the timer, preserving the time remaining until next trigger.\n"
|
|
||||||
"The timer can be resumed later with resume()."},
|
|
||||||
{"resume", (PyCFunction)PyTimer::resume, METH_NOARGS,
|
{"resume", (PyCFunction)PyTimer::resume, METH_NOARGS,
|
||||||
"resume() -> None\n\n"
|
"Resume a paused timer"},
|
||||||
"Resume a paused timer from where it left off.\n"
|
|
||||||
"Has no effect if the timer is not paused."},
|
|
||||||
{"cancel", (PyCFunction)PyTimer::cancel, METH_NOARGS,
|
{"cancel", (PyCFunction)PyTimer::cancel, METH_NOARGS,
|
||||||
"cancel() -> None\n\n"
|
"Cancel the timer and remove it from the system"},
|
||||||
"Cancel the timer and remove it from the timer system.\n"
|
|
||||||
"The timer will no longer fire and cannot be restarted."},
|
|
||||||
{"restart", (PyCFunction)PyTimer::restart, METH_NOARGS,
|
{"restart", (PyCFunction)PyTimer::restart, METH_NOARGS,
|
||||||
"restart() -> None\n\n"
|
"Restart the timer from the current time"},
|
||||||
"Restart the timer from the beginning.\n"
|
|
||||||
"Resets the timer to fire after a full interval from now."},
|
|
||||||
{NULL}
|
{NULL}
|
||||||
};
|
};
|
|
@ -4,13 +4,12 @@
|
||||||
#include <memory>
|
#include <memory>
|
||||||
#include <string>
|
#include <string>
|
||||||
|
|
||||||
class Timer;
|
class PyTimerCallable;
|
||||||
|
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<Timer> data;
|
std::shared_ptr<PyTimerCallable> data;
|
||||||
std::string name;
|
std::string name;
|
||||||
PyObject* weakreflist; // Weak reference support
|
|
||||||
} PyTimerObject;
|
} PyTimerObject;
|
||||||
|
|
||||||
class PyTimer
|
class PyTimer
|
||||||
|
@ -29,7 +28,6 @@ public:
|
||||||
static PyObject* restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored));
|
static PyObject* restart(PyTimerObject* self, PyObject* Py_UNUSED(ignored));
|
||||||
|
|
||||||
// Timer property getters
|
// Timer property getters
|
||||||
static PyObject* get_name(PyTimerObject* self, void* closure);
|
|
||||||
static PyObject* get_interval(PyTimerObject* self, void* closure);
|
static PyObject* get_interval(PyTimerObject* self, void* closure);
|
||||||
static int set_interval(PyTimerObject* self, PyObject* value, void* closure);
|
static int set_interval(PyTimerObject* self, PyObject* value, void* closure);
|
||||||
static PyObject* get_remaining(PyTimerObject* self, void* closure);
|
static PyObject* get_remaining(PyTimerObject* self, void* closure);
|
||||||
|
@ -37,8 +35,6 @@ public:
|
||||||
static PyObject* get_active(PyTimerObject* self, void* closure);
|
static PyObject* get_active(PyTimerObject* self, void* closure);
|
||||||
static PyObject* get_callback(PyTimerObject* self, void* closure);
|
static PyObject* get_callback(PyTimerObject* self, void* closure);
|
||||||
static int set_callback(PyTimerObject* self, PyObject* value, void* closure);
|
static int set_callback(PyTimerObject* self, PyObject* value, void* closure);
|
||||||
static PyObject* get_once(PyTimerObject* self, void* closure);
|
|
||||||
static int set_once(PyTimerObject* self, PyObject* value, void* closure);
|
|
||||||
|
|
||||||
static PyGetSetDef getsetters[];
|
static PyGetSetDef getsetters[];
|
||||||
static PyMethodDef methods[];
|
static PyMethodDef methods[];
|
||||||
|
@ -53,35 +49,7 @@ namespace mcrfpydef {
|
||||||
.tp_dealloc = (destructor)PyTimer::dealloc,
|
.tp_dealloc = (destructor)PyTimer::dealloc,
|
||||||
.tp_repr = PyTimer::repr,
|
.tp_repr = PyTimer::repr,
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Timer(name, callback, interval, once=False)\n\n"
|
.tp_doc = PyDoc_STR("Timer object for scheduled callbacks"),
|
||||||
"Create a timer that calls a function at regular intervals.\n\n"
|
|
||||||
"Args:\n"
|
|
||||||
" name (str): Unique identifier for the timer\n"
|
|
||||||
" callback (callable): Function to call - receives (timer, runtime) args\n"
|
|
||||||
" interval (int): Time between calls in milliseconds\n"
|
|
||||||
" once (bool): If True, timer stops after first call. Default: False\n\n"
|
|
||||||
"Attributes:\n"
|
|
||||||
" interval (int): Time between calls in milliseconds\n"
|
|
||||||
" remaining (int): Time until next call in milliseconds (read-only)\n"
|
|
||||||
" paused (bool): Whether timer is paused (read-only)\n"
|
|
||||||
" active (bool): Whether timer is active and not paused (read-only)\n"
|
|
||||||
" callback (callable): The callback function\n"
|
|
||||||
" once (bool): Whether timer stops after firing once\n\n"
|
|
||||||
"Methods:\n"
|
|
||||||
" pause(): Pause the timer, preserving time remaining\n"
|
|
||||||
" resume(): Resume a paused timer\n"
|
|
||||||
" cancel(): Stop and remove the timer\n"
|
|
||||||
" restart(): Reset timer to start from beginning\n\n"
|
|
||||||
"Example:\n"
|
|
||||||
" def on_timer(timer, runtime):\n"
|
|
||||||
" print(f'Timer {timer} fired at {runtime}ms')\n"
|
|
||||||
" if runtime > 5000:\n"
|
|
||||||
" timer.cancel()\n"
|
|
||||||
" \n"
|
|
||||||
" timer = mcrfpy.Timer('my_timer', on_timer, 1000)\n"
|
|
||||||
" timer.pause() # Pause timer\n"
|
|
||||||
" timer.resume() # Resume timer\n"
|
|
||||||
" timer.once = True # Make it one-shot"),
|
|
||||||
.tp_methods = PyTimer::methods,
|
.tp_methods = PyTimer::methods,
|
||||||
.tp_getset = PyTimer::getsetters,
|
.tp_getset = PyTimer::getsetters,
|
||||||
.tp_init = (initproc)PyTimer::init,
|
.tp_init = (initproc)PyTimer::init,
|
||||||
|
|
|
@ -1,85 +0,0 @@
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
#include <iostream>
|
|
||||||
|
|
||||||
PythonObjectCache* PythonObjectCache::instance = nullptr;
|
|
||||||
|
|
||||||
PythonObjectCache& PythonObjectCache::getInstance() {
|
|
||||||
if (!instance) {
|
|
||||||
instance = new PythonObjectCache();
|
|
||||||
}
|
|
||||||
return *instance;
|
|
||||||
}
|
|
||||||
|
|
||||||
PythonObjectCache::~PythonObjectCache() {
|
|
||||||
clear();
|
|
||||||
}
|
|
||||||
|
|
||||||
uint64_t PythonObjectCache::assignSerial() {
|
|
||||||
return next_serial.fetch_add(1, std::memory_order_relaxed);
|
|
||||||
}
|
|
||||||
|
|
||||||
void PythonObjectCache::registerObject(uint64_t serial, PyObject* weakref) {
|
|
||||||
if (!weakref || serial == 0) return;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(serial_mutex);
|
|
||||||
|
|
||||||
// Clean up any existing entry
|
|
||||||
auto it = cache.find(serial);
|
|
||||||
if (it != cache.end()) {
|
|
||||||
Py_DECREF(it->second);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store the new weak reference
|
|
||||||
Py_INCREF(weakref);
|
|
||||||
cache[serial] = weakref;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* PythonObjectCache::lookup(uint64_t serial) {
|
|
||||||
if (serial == 0) return nullptr;
|
|
||||||
|
|
||||||
// No mutex needed for read - GIL protects PyWeakref_GetObject
|
|
||||||
auto it = cache.find(serial);
|
|
||||||
if (it != cache.end()) {
|
|
||||||
PyObject* obj = PyWeakref_GetObject(it->second);
|
|
||||||
if (obj && obj != Py_None) {
|
|
||||||
Py_INCREF(obj);
|
|
||||||
return obj;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return nullptr;
|
|
||||||
}
|
|
||||||
|
|
||||||
void PythonObjectCache::remove(uint64_t serial) {
|
|
||||||
if (serial == 0) return;
|
|
||||||
|
|
||||||
std::lock_guard<std::mutex> lock(serial_mutex);
|
|
||||||
auto it = cache.find(serial);
|
|
||||||
if (it != cache.end()) {
|
|
||||||
Py_DECREF(it->second);
|
|
||||||
cache.erase(it);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PythonObjectCache::cleanup() {
|
|
||||||
std::lock_guard<std::mutex> lock(serial_mutex);
|
|
||||||
|
|
||||||
auto it = cache.begin();
|
|
||||||
while (it != cache.end()) {
|
|
||||||
PyObject* obj = PyWeakref_GetObject(it->second);
|
|
||||||
if (!obj || obj == Py_None) {
|
|
||||||
Py_DECREF(it->second);
|
|
||||||
it = cache.erase(it);
|
|
||||||
} else {
|
|
||||||
++it;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void PythonObjectCache::clear() {
|
|
||||||
std::lock_guard<std::mutex> lock(serial_mutex);
|
|
||||||
|
|
||||||
for (auto& pair : cache) {
|
|
||||||
Py_DECREF(pair.second);
|
|
||||||
}
|
|
||||||
cache.clear();
|
|
||||||
}
|
|
|
@ -1,40 +0,0 @@
|
||||||
#pragma once
|
|
||||||
|
|
||||||
#include <Python.h>
|
|
||||||
#include <unordered_map>
|
|
||||||
#include <mutex>
|
|
||||||
#include <atomic>
|
|
||||||
#include <cstdint>
|
|
||||||
|
|
||||||
class PythonObjectCache {
|
|
||||||
private:
|
|
||||||
static PythonObjectCache* instance;
|
|
||||||
std::mutex serial_mutex;
|
|
||||||
std::atomic<uint64_t> next_serial{1};
|
|
||||||
std::unordered_map<uint64_t, PyObject*> cache;
|
|
||||||
|
|
||||||
PythonObjectCache() = default;
|
|
||||||
~PythonObjectCache();
|
|
||||||
|
|
||||||
public:
|
|
||||||
static PythonObjectCache& getInstance();
|
|
||||||
|
|
||||||
// Assign a new serial number
|
|
||||||
uint64_t assignSerial();
|
|
||||||
|
|
||||||
// Register a Python object with a serial number
|
|
||||||
void registerObject(uint64_t serial, PyObject* weakref);
|
|
||||||
|
|
||||||
// Lookup a Python object by serial number
|
|
||||||
// Returns new reference or nullptr
|
|
||||||
PyObject* lookup(uint64_t serial);
|
|
||||||
|
|
||||||
// Remove an entry from the cache
|
|
||||||
void remove(uint64_t serial);
|
|
||||||
|
|
||||||
// Clean up dead weak references
|
|
||||||
void cleanup();
|
|
||||||
|
|
||||||
// Clear entire cache (for module cleanup)
|
|
||||||
void clear();
|
|
||||||
};
|
|
127
src/Timer.cpp
127
src/Timer.cpp
|
@ -1,140 +1,31 @@
|
||||||
#include "Timer.h"
|
#include "Timer.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
#include "PyCallable.h"
|
|
||||||
|
|
||||||
Timer::Timer(PyObject* _target, int _interval, int now, bool _once)
|
Timer::Timer(PyObject* _target, int _interval, int now)
|
||||||
: callback(std::make_shared<PyCallable>(_target)), interval(_interval), last_ran(now),
|
: target(_target), interval(_interval), last_ran(now)
|
||||||
paused(false), pause_start_time(0), total_paused_time(0), once(_once)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Timer::Timer()
|
Timer::Timer()
|
||||||
: callback(std::make_shared<PyCallable>(Py_None)), interval(0), last_ran(0),
|
: target(Py_None), interval(0), last_ran(0)
|
||||||
paused(false), pause_start_time(0), total_paused_time(0), once(false)
|
|
||||||
{}
|
{}
|
||||||
|
|
||||||
Timer::~Timer() {
|
|
||||||
if (serial_number != 0) {
|
|
||||||
PythonObjectCache::getInstance().remove(serial_number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Timer::hasElapsed(int now) const
|
|
||||||
{
|
|
||||||
if (paused) return false;
|
|
||||||
return now >= last_ran + interval;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Timer::test(int now)
|
bool Timer::test(int now)
|
||||||
{
|
{
|
||||||
if (!callback || callback->isNone()) return false;
|
if (!target || target == Py_None) return false;
|
||||||
|
if (now > last_ran + interval)
|
||||||
if (hasElapsed(now))
|
|
||||||
{
|
{
|
||||||
last_ran = now;
|
last_ran = now;
|
||||||
|
PyObject* args = Py_BuildValue("(i)", now);
|
||||||
// Get the PyTimer wrapper from cache to pass to callback
|
PyObject* retval = PyObject_Call(target, args, NULL);
|
||||||
PyObject* timer_obj = nullptr;
|
|
||||||
if (serial_number != 0) {
|
|
||||||
timer_obj = PythonObjectCache::getInstance().lookup(serial_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build args: (timer, runtime) or just (runtime) if no wrapper found
|
|
||||||
PyObject* args;
|
|
||||||
if (timer_obj) {
|
|
||||||
args = Py_BuildValue("(Oi)", timer_obj, now);
|
|
||||||
} else {
|
|
||||||
// Fallback to old behavior if no wrapper found
|
|
||||||
args = Py_BuildValue("(i)", now);
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* retval = callback->call(args, NULL);
|
|
||||||
Py_DECREF(args);
|
|
||||||
|
|
||||||
if (!retval)
|
if (!retval)
|
||||||
{
|
{
|
||||||
std::cout << "Timer callback has raised an exception. It's going to STDERR and being dropped:" << std::endl;
|
std::cout << "timer has raised an exception. It's going to STDERR and being dropped:" << std::endl;
|
||||||
PyErr_Print();
|
PyErr_Print();
|
||||||
PyErr_Clear();
|
PyErr_Clear();
|
||||||
} else if (retval != Py_None)
|
} else if (retval != Py_None)
|
||||||
{
|
{
|
||||||
std::cout << "Timer returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
|
std::cout << "timer returned a non-None value. It's not an error, it's just not being saved or used." << std::endl;
|
||||||
Py_DECREF(retval);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handle one-shot timers
|
|
||||||
if (once) {
|
|
||||||
cancel();
|
|
||||||
}
|
|
||||||
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Timer::pause(int current_time)
|
|
||||||
{
|
|
||||||
if (!paused) {
|
|
||||||
paused = true;
|
|
||||||
pause_start_time = current_time;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::resume(int current_time)
|
|
||||||
{
|
|
||||||
if (paused) {
|
|
||||||
paused = false;
|
|
||||||
int paused_duration = current_time - pause_start_time;
|
|
||||||
total_paused_time += paused_duration;
|
|
||||||
// Adjust last_ran to account for the pause
|
|
||||||
last_ran += paused_duration;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::restart(int current_time)
|
|
||||||
{
|
|
||||||
last_ran = current_time;
|
|
||||||
paused = false;
|
|
||||||
pause_start_time = 0;
|
|
||||||
total_paused_time = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::cancel()
|
|
||||||
{
|
|
||||||
// Cancel by setting callback to None
|
|
||||||
callback = std::make_shared<PyCallable>(Py_None);
|
|
||||||
}
|
|
||||||
|
|
||||||
bool Timer::isActive() const
|
|
||||||
{
|
|
||||||
return callback && !callback->isNone() && !paused;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Timer::getRemaining(int current_time) const
|
|
||||||
{
|
|
||||||
if (paused) {
|
|
||||||
// When paused, calculate time remaining from when it was paused
|
|
||||||
int elapsed_when_paused = pause_start_time - last_ran;
|
|
||||||
return interval - elapsed_when_paused;
|
|
||||||
}
|
|
||||||
int elapsed = current_time - last_ran;
|
|
||||||
return interval - elapsed;
|
|
||||||
}
|
|
||||||
|
|
||||||
int Timer::getElapsed(int current_time) const
|
|
||||||
{
|
|
||||||
if (paused) {
|
|
||||||
return pause_start_time - last_ran;
|
|
||||||
}
|
|
||||||
return current_time - last_ran;
|
|
||||||
}
|
|
||||||
|
|
||||||
PyObject* Timer::getCallback()
|
|
||||||
{
|
|
||||||
if (!callback) return Py_None;
|
|
||||||
return callback->borrow();
|
|
||||||
}
|
|
||||||
|
|
||||||
void Timer::setCallback(PyObject* new_callback)
|
|
||||||
{
|
|
||||||
callback = std::make_shared<PyCallable>(new_callback);
|
|
||||||
}
|
|
47
src/Timer.h
47
src/Timer.h
|
@ -1,54 +1,15 @@
|
||||||
#pragma once
|
#pragma once
|
||||||
#include "Common.h"
|
#include "Common.h"
|
||||||
#include "Python.h"
|
#include "Python.h"
|
||||||
#include <memory>
|
|
||||||
|
|
||||||
class PyCallable;
|
|
||||||
class GameEngine; // forward declare
|
class GameEngine; // forward declare
|
||||||
|
|
||||||
class Timer
|
class Timer
|
||||||
{
|
{
|
||||||
private:
|
public:
|
||||||
std::shared_ptr<PyCallable> callback;
|
PyObject* target;
|
||||||
int interval;
|
int interval;
|
||||||
int last_ran;
|
int last_ran;
|
||||||
|
|
||||||
// Pause/resume support
|
|
||||||
bool paused;
|
|
||||||
int pause_start_time;
|
|
||||||
int total_paused_time;
|
|
||||||
|
|
||||||
// One-shot timer support
|
|
||||||
bool once;
|
|
||||||
|
|
||||||
public:
|
|
||||||
uint64_t serial_number = 0; // For Python object cache
|
|
||||||
|
|
||||||
Timer(); // for map to build
|
Timer(); // for map to build
|
||||||
Timer(PyObject* target, int interval, int now, bool once = false);
|
Timer(PyObject*, int, int);
|
||||||
~Timer();
|
bool test(int);
|
||||||
|
|
||||||
// Core timer functionality
|
|
||||||
bool test(int now);
|
|
||||||
bool hasElapsed(int now) const;
|
|
||||||
|
|
||||||
// Timer control methods
|
|
||||||
void pause(int current_time);
|
|
||||||
void resume(int current_time);
|
|
||||||
void restart(int current_time);
|
|
||||||
void cancel();
|
|
||||||
|
|
||||||
// Timer state queries
|
|
||||||
bool isPaused() const { return paused; }
|
|
||||||
bool isActive() const;
|
|
||||||
int getInterval() const { return interval; }
|
|
||||||
void setInterval(int new_interval) { interval = new_interval; }
|
|
||||||
int getRemaining(int current_time) const;
|
|
||||||
int getElapsed(int current_time) const;
|
|
||||||
bool isOnce() const { return once; }
|
|
||||||
void setOnce(bool value) { once = value; }
|
|
||||||
|
|
||||||
// Callback management
|
|
||||||
PyObject* getCallback();
|
|
||||||
void setCallback(PyObject* new_callback);
|
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,14 +6,12 @@ class UIEntity;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<UIEntity> data;
|
std::shared_ptr<UIEntity> data;
|
||||||
PyObject* weakreflist; // Weak reference support
|
|
||||||
} PyUIEntityObject;
|
} PyUIEntityObject;
|
||||||
|
|
||||||
class UIFrame;
|
class UIFrame;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<UIFrame> data;
|
std::shared_ptr<UIFrame> data;
|
||||||
PyObject* weakreflist; // Weak reference support
|
|
||||||
} PyUIFrameObject;
|
} PyUIFrameObject;
|
||||||
|
|
||||||
class UICaption;
|
class UICaption;
|
||||||
|
@ -21,21 +19,18 @@ typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<UICaption> data;
|
std::shared_ptr<UICaption> data;
|
||||||
PyObject* font;
|
PyObject* font;
|
||||||
PyObject* weakreflist; // Weak reference support
|
|
||||||
} PyUICaptionObject;
|
} PyUICaptionObject;
|
||||||
|
|
||||||
class UIGrid;
|
class UIGrid;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<UIGrid> data;
|
std::shared_ptr<UIGrid> data;
|
||||||
PyObject* weakreflist; // Weak reference support
|
|
||||||
} PyUIGridObject;
|
} PyUIGridObject;
|
||||||
|
|
||||||
class UISprite;
|
class UISprite;
|
||||||
typedef struct {
|
typedef struct {
|
||||||
PyObject_HEAD
|
PyObject_HEAD
|
||||||
std::shared_ptr<UISprite> data;
|
std::shared_ptr<UISprite> data;
|
||||||
PyObject* weakreflist; // Weak reference support
|
|
||||||
} PyUISpriteObject;
|
} PyUISpriteObject;
|
||||||
|
|
||||||
// Common Python method implementations for UIDrawable-derived classes
|
// Common Python method implementations for UIDrawable-derived classes
|
||||||
|
|
|
@ -3,7 +3,6 @@
|
||||||
#include "PyColor.h"
|
#include "PyColor.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
// UIDrawable methods now in UIBase.h
|
// UIDrawable methods now in UIBase.h
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
@ -440,19 +439,6 @@ int UICaption::init(PyUICaptionObject* self, PyObject* args, PyObject* kwds)
|
||||||
self->data->click_register(click_handler);
|
self->data->click_register(click_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize weak reference list
|
|
||||||
self->weakreflist = NULL;
|
|
||||||
|
|
||||||
// Register in Python object cache
|
|
||||||
if (self->data->serial_number == 0) {
|
|
||||||
self->data->serial_number = PythonObjectCache::getInstance().assignSerial();
|
|
||||||
PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL);
|
|
||||||
if (weakref) {
|
|
||||||
PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref);
|
|
||||||
Py_DECREF(weakref); // Cache owns the reference now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -54,10 +54,6 @@ namespace mcrfpydef {
|
||||||
.tp_dealloc = (destructor)[](PyObject* self)
|
.tp_dealloc = (destructor)[](PyObject* self)
|
||||||
{
|
{
|
||||||
PyUICaptionObject* obj = (PyUICaptionObject*)self;
|
PyUICaptionObject* obj = (PyUICaptionObject*)self;
|
||||||
// Clear weak references
|
|
||||||
if (obj->weakreflist != NULL) {
|
|
||||||
PyObject_ClearWeakRefs(self);
|
|
||||||
}
|
|
||||||
// TODO - reevaluate with PyFont usage; UICaption does not own the font
|
// TODO - reevaluate with PyFont usage; UICaption does not own the font
|
||||||
// release reference to font object
|
// release reference to font object
|
||||||
if (obj->font) Py_DECREF(obj->font);
|
if (obj->font) Py_DECREF(obj->font);
|
||||||
|
@ -68,7 +64,7 @@ namespace mcrfpydef {
|
||||||
//.tp_hash = NULL,
|
//.tp_hash = NULL,
|
||||||
//.tp_iter
|
//.tp_iter
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Caption(pos=None, font=None, text='', **kwargs)\n\n"
|
.tp_doc = PyDoc_STR("Caption(pos=None, font=None, text='', **kwargs)\n\n"
|
||||||
"A text display UI element with customizable font and styling.\n\n"
|
"A text display UI element with customizable font and styling.\n\n"
|
||||||
"Args:\n"
|
"Args:\n"
|
||||||
|
@ -110,11 +106,7 @@ namespace mcrfpydef {
|
||||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
||||||
{
|
{
|
||||||
PyUICaptionObject* self = (PyUICaptionObject*)type->tp_alloc(type, 0);
|
PyUICaptionObject* self = (PyUICaptionObject*)type->tp_alloc(type, 0);
|
||||||
if (self) {
|
if (self) self->data = std::make_shared<UICaption>();
|
||||||
self->data = std::make_shared<UICaption>();
|
|
||||||
self->font = nullptr;
|
|
||||||
self->weakreflist = nullptr;
|
|
||||||
}
|
|
||||||
return (PyObject*)self;
|
return (PyObject*)self;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
#include <climits>
|
#include <climits>
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
|
|
||||||
|
@ -18,14 +17,6 @@ static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
|
||||||
Py_RETURN_NONE;
|
Py_RETURN_NONE;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Check cache first
|
|
||||||
if (drawable->serial_number != 0) {
|
|
||||||
PyObject* cached = PythonObjectCache::getInstance().lookup(drawable->serial_number);
|
|
||||||
if (cached) {
|
|
||||||
return cached; // Already INCREF'd by lookup
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
PyTypeObject* type = nullptr;
|
PyTypeObject* type = nullptr;
|
||||||
PyObject* obj = nullptr;
|
PyObject* obj = nullptr;
|
||||||
|
|
||||||
|
@ -37,7 +28,6 @@ static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
|
||||||
auto pyObj = (PyUIFrameObject*)type->tp_alloc(type, 0);
|
auto pyObj = (PyUIFrameObject*)type->tp_alloc(type, 0);
|
||||||
if (pyObj) {
|
if (pyObj) {
|
||||||
pyObj->data = std::static_pointer_cast<UIFrame>(drawable);
|
pyObj->data = std::static_pointer_cast<UIFrame>(drawable);
|
||||||
pyObj->weakreflist = NULL;
|
|
||||||
}
|
}
|
||||||
obj = (PyObject*)pyObj;
|
obj = (PyObject*)pyObj;
|
||||||
break;
|
break;
|
||||||
|
@ -50,7 +40,6 @@ static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
|
||||||
if (pyObj) {
|
if (pyObj) {
|
||||||
pyObj->data = std::static_pointer_cast<UICaption>(drawable);
|
pyObj->data = std::static_pointer_cast<UICaption>(drawable);
|
||||||
pyObj->font = nullptr;
|
pyObj->font = nullptr;
|
||||||
pyObj->weakreflist = NULL;
|
|
||||||
}
|
}
|
||||||
obj = (PyObject*)pyObj;
|
obj = (PyObject*)pyObj;
|
||||||
break;
|
break;
|
||||||
|
@ -62,7 +51,6 @@ static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
|
||||||
auto pyObj = (PyUISpriteObject*)type->tp_alloc(type, 0);
|
auto pyObj = (PyUISpriteObject*)type->tp_alloc(type, 0);
|
||||||
if (pyObj) {
|
if (pyObj) {
|
||||||
pyObj->data = std::static_pointer_cast<UISprite>(drawable);
|
pyObj->data = std::static_pointer_cast<UISprite>(drawable);
|
||||||
pyObj->weakreflist = NULL;
|
|
||||||
}
|
}
|
||||||
obj = (PyObject*)pyObj;
|
obj = (PyObject*)pyObj;
|
||||||
break;
|
break;
|
||||||
|
@ -74,7 +62,6 @@ static PyObject* convertDrawableToPython(std::shared_ptr<UIDrawable> drawable) {
|
||||||
auto pyObj = (PyUIGridObject*)type->tp_alloc(type, 0);
|
auto pyObj = (PyUIGridObject*)type->tp_alloc(type, 0);
|
||||||
if (pyObj) {
|
if (pyObj) {
|
||||||
pyObj->data = std::static_pointer_cast<UIGrid>(drawable);
|
pyObj->data = std::static_pointer_cast<UIGrid>(drawable);
|
||||||
pyObj->weakreflist = NULL;
|
|
||||||
}
|
}
|
||||||
obj = (PyObject*)pyObj;
|
obj = (PyObject*)pyObj;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -5,113 +5,9 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
|
|
||||||
UIDrawable::UIDrawable() : position(0.0f, 0.0f) { click_callable = NULL; }
|
UIDrawable::UIDrawable() : position(0.0f, 0.0f) { click_callable = NULL; }
|
||||||
|
|
||||||
UIDrawable::UIDrawable(const UIDrawable& other)
|
|
||||||
: z_index(other.z_index),
|
|
||||||
name(other.name),
|
|
||||||
position(other.position),
|
|
||||||
visible(other.visible),
|
|
||||||
opacity(other.opacity),
|
|
||||||
serial_number(0), // Don't copy serial number
|
|
||||||
use_render_texture(other.use_render_texture),
|
|
||||||
render_dirty(true) // Force redraw after copy
|
|
||||||
{
|
|
||||||
// Deep copy click_callable if it exists
|
|
||||||
if (other.click_callable) {
|
|
||||||
click_callable = std::make_unique<PyClickCallable>(*other.click_callable);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deep copy render texture if needed
|
|
||||||
if (other.render_texture && other.use_render_texture) {
|
|
||||||
auto size = other.render_texture->getSize();
|
|
||||||
enableRenderTexture(size.x, size.y);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
UIDrawable& UIDrawable::operator=(const UIDrawable& other) {
|
|
||||||
if (this != &other) {
|
|
||||||
// Copy basic members
|
|
||||||
z_index = other.z_index;
|
|
||||||
name = other.name;
|
|
||||||
position = other.position;
|
|
||||||
visible = other.visible;
|
|
||||||
opacity = other.opacity;
|
|
||||||
use_render_texture = other.use_render_texture;
|
|
||||||
render_dirty = true; // Force redraw after copy
|
|
||||||
|
|
||||||
// Deep copy click_callable
|
|
||||||
if (other.click_callable) {
|
|
||||||
click_callable = std::make_unique<PyClickCallable>(*other.click_callable);
|
|
||||||
} else {
|
|
||||||
click_callable.reset();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Deep copy render texture if needed
|
|
||||||
if (other.render_texture && other.use_render_texture) {
|
|
||||||
auto size = other.render_texture->getSize();
|
|
||||||
enableRenderTexture(size.x, size.y);
|
|
||||||
} else {
|
|
||||||
render_texture.reset();
|
|
||||||
use_render_texture = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIDrawable::UIDrawable(UIDrawable&& other) noexcept
|
|
||||||
: z_index(other.z_index),
|
|
||||||
name(std::move(other.name)),
|
|
||||||
position(other.position),
|
|
||||||
visible(other.visible),
|
|
||||||
opacity(other.opacity),
|
|
||||||
serial_number(other.serial_number),
|
|
||||||
click_callable(std::move(other.click_callable)),
|
|
||||||
render_texture(std::move(other.render_texture)),
|
|
||||||
render_sprite(std::move(other.render_sprite)),
|
|
||||||
use_render_texture(other.use_render_texture),
|
|
||||||
render_dirty(other.render_dirty)
|
|
||||||
{
|
|
||||||
// Clear the moved-from object's serial number to avoid cache issues
|
|
||||||
other.serial_number = 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIDrawable& UIDrawable::operator=(UIDrawable&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
// Clear our own cache entry if we have one
|
|
||||||
if (serial_number != 0) {
|
|
||||||
PythonObjectCache::getInstance().remove(serial_number);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Move basic members
|
|
||||||
z_index = other.z_index;
|
|
||||||
name = std::move(other.name);
|
|
||||||
position = other.position;
|
|
||||||
visible = other.visible;
|
|
||||||
opacity = other.opacity;
|
|
||||||
serial_number = other.serial_number;
|
|
||||||
use_render_texture = other.use_render_texture;
|
|
||||||
render_dirty = other.render_dirty;
|
|
||||||
|
|
||||||
// Move unique_ptr members
|
|
||||||
click_callable = std::move(other.click_callable);
|
|
||||||
render_texture = std::move(other.render_texture);
|
|
||||||
render_sprite = std::move(other.render_sprite);
|
|
||||||
|
|
||||||
// Clear the moved-from object's serial number
|
|
||||||
other.serial_number = 0;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
UIDrawable::~UIDrawable() {
|
|
||||||
if (serial_number != 0) {
|
|
||||||
PythonObjectCache::getInstance().remove(serial_number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
void UIDrawable::click_unregister()
|
void UIDrawable::click_unregister()
|
||||||
{
|
{
|
||||||
click_callable.reset();
|
click_callable.reset();
|
||||||
|
|
|
@ -39,15 +39,6 @@ public:
|
||||||
void click_unregister();
|
void click_unregister();
|
||||||
|
|
||||||
UIDrawable();
|
UIDrawable();
|
||||||
virtual ~UIDrawable();
|
|
||||||
|
|
||||||
// Copy constructor and assignment operator
|
|
||||||
UIDrawable(const UIDrawable& other);
|
|
||||||
UIDrawable& operator=(const UIDrawable& other);
|
|
||||||
|
|
||||||
// Move constructor and assignment operator
|
|
||||||
UIDrawable(UIDrawable&& other) noexcept;
|
|
||||||
UIDrawable& operator=(UIDrawable&& other) noexcept;
|
|
||||||
|
|
||||||
static PyObject* get_click(PyObject* self, void* closure);
|
static PyObject* get_click(PyObject* self, void* closure);
|
||||||
static int set_click(PyObject* self, PyObject* value, void* closure);
|
static int set_click(PyObject* self, PyObject* value, void* closure);
|
||||||
|
@ -99,9 +90,6 @@ public:
|
||||||
virtual bool getProperty(const std::string& name, sf::Vector2f& value) const { return false; }
|
virtual bool getProperty(const std::string& name, sf::Vector2f& value) const { return false; }
|
||||||
virtual bool getProperty(const std::string& name, std::string& value) const { return false; }
|
virtual bool getProperty(const std::string& name, std::string& value) const { return false; }
|
||||||
|
|
||||||
// Python object cache support
|
|
||||||
uint64_t serial_number = 0;
|
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
// RenderTexture support (opt-in)
|
// RenderTexture support (opt-in)
|
||||||
std::unique_ptr<sf::RenderTexture> render_texture;
|
std::unique_ptr<sf::RenderTexture> render_texture;
|
||||||
|
|
|
@ -4,7 +4,6 @@
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
#include "PyObjectUtils.h"
|
#include "PyObjectUtils.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
// UIDrawable methods now in UIBase.h
|
// UIDrawable methods now in UIBase.h
|
||||||
#include "UIEntityPyMethods.h"
|
#include "UIEntityPyMethods.h"
|
||||||
|
|
||||||
|
@ -17,12 +16,6 @@ UIEntity::UIEntity()
|
||||||
// gridstate vector starts empty - will be lazily initialized when needed
|
// gridstate vector starts empty - will be lazily initialized when needed
|
||||||
}
|
}
|
||||||
|
|
||||||
UIEntity::~UIEntity() {
|
|
||||||
if (serial_number != 0) {
|
|
||||||
PythonObjectCache::getInstance().remove(serial_number);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Removed UIEntity(UIGrid&) constructor - using lazy initialization instead
|
// Removed UIEntity(UIGrid&) constructor - using lazy initialization instead
|
||||||
|
|
||||||
void UIEntity::updateVisibility()
|
void UIEntity::updateVisibility()
|
||||||
|
@ -194,20 +187,7 @@ int UIEntity::init(PyUIEntityObject* self, PyObject* args, PyObject* kwds) {
|
||||||
// Create the entity
|
// Create the entity
|
||||||
self->data = std::make_shared<UIEntity>();
|
self->data = std::make_shared<UIEntity>();
|
||||||
|
|
||||||
// Initialize weak reference list
|
// Store reference to Python object
|
||||||
self->weakreflist = NULL;
|
|
||||||
|
|
||||||
// Register in Python object cache
|
|
||||||
if (self->data->serial_number == 0) {
|
|
||||||
self->data->serial_number = PythonObjectCache::getInstance().assignSerial();
|
|
||||||
PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL);
|
|
||||||
if (weakref) {
|
|
||||||
PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref);
|
|
||||||
Py_DECREF(weakref); // Cache owns the reference now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Store reference to Python object (legacy - to be removed)
|
|
||||||
self->data->self = (PyObject*)self;
|
self->data->self = (PyObject*)self;
|
||||||
Py_INCREF(self);
|
Py_INCREF(self);
|
||||||
|
|
||||||
|
|
|
@ -14,37 +14,12 @@
|
||||||
#include "PyFont.h"
|
#include "PyFont.h"
|
||||||
|
|
||||||
#include "UIGridPoint.h"
|
#include "UIGridPoint.h"
|
||||||
|
#include "UIDrawable.h"
|
||||||
#include "UIBase.h"
|
#include "UIBase.h"
|
||||||
#include "UISprite.h"
|
#include "UISprite.h"
|
||||||
|
|
||||||
class UIGrid;
|
class UIGrid;
|
||||||
|
|
||||||
// UIEntity
|
|
||||||
/*
|
|
||||||
|
|
||||||
****************************************
|
|
||||||
* say it with me: *
|
|
||||||
* ✨ UIEntity is not a UIDrawable ✨ *
|
|
||||||
****************************************
|
|
||||||
|
|
||||||
**Why Not, John?**
|
|
||||||
Doesn't it say "UI" on the front of it?
|
|
||||||
It sure does. Probably should have called it "GridEntity", but it's a bit late now.
|
|
||||||
|
|
||||||
UIDrawables have positions in **screen pixel coordinates**. Their position is an offset from their parent's position, and they are deeply nestable (Scene -> Frame -> Frame -> ...)
|
|
||||||
|
|
||||||
However:
|
|
||||||
UIEntity has a position in **Grid tile coordinates**.
|
|
||||||
UIEntity is not nestable at all. Grid -> Entity.
|
|
||||||
UIEntity has a strict one/none relationship with a Grid: if you add it to another grid, it will have itself removed from the losing grid's collection.
|
|
||||||
UIEntity originally only allowed a single-tile sprite, but around mid-July 2025, I'm working to change that to allow any UIDrawable to go there, or multi-tile sprites.
|
|
||||||
UIEntity is, at its core, the container for *a perspective of map data*.
|
|
||||||
The Grid should contain the true, complete contents of the game's space, and the Entity should use pathfinding, field of view, and game logic to interact with the Grid's layer data.
|
|
||||||
|
|
||||||
In Conclusion, because UIEntity cannot be drawn on a Frame or Scene, and has the unique role of serving as a filter of the data contained in a Grid, UIEntity will not become a UIDrawable.
|
|
||||||
|
|
||||||
*/
|
|
||||||
|
|
||||||
//class UIEntity;
|
//class UIEntity;
|
||||||
//typedef struct {
|
//typedef struct {
|
||||||
// PyObject_HEAD
|
// PyObject_HEAD
|
||||||
|
@ -57,11 +32,11 @@ sf::Vector2f PyObject_to_sfVector2f(PyObject* obj);
|
||||||
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state);
|
PyObject* UIGridPointState_to_PyObject(const UIGridPointState& state);
|
||||||
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec);
|
PyObject* UIGridPointStateVector_to_PyList(const std::vector<UIGridPointState>& vec);
|
||||||
|
|
||||||
class UIEntity
|
// TODO: make UIEntity a drawable
|
||||||
|
class UIEntity//: public UIDrawable
|
||||||
{
|
{
|
||||||
public:
|
public:
|
||||||
PyObject* self = nullptr; // Reference to the Python object (if created from Python)
|
PyObject* self = nullptr; // Reference to the Python object (if created from Python)
|
||||||
uint64_t serial_number = 0; // For Python object cache
|
|
||||||
std::shared_ptr<UIGrid> grid;
|
std::shared_ptr<UIGrid> grid;
|
||||||
std::vector<UIGridPointState> gridstate;
|
std::vector<UIGridPointState> gridstate;
|
||||||
UISprite sprite;
|
UISprite sprite;
|
||||||
|
@ -69,7 +44,6 @@ public:
|
||||||
//void render(sf::Vector2f); //override final;
|
//void render(sf::Vector2f); //override final;
|
||||||
|
|
||||||
UIEntity();
|
UIEntity();
|
||||||
~UIEntity();
|
|
||||||
|
|
||||||
// Visibility methods
|
// Visibility methods
|
||||||
void updateVisibility(); // Update gridstate from current FOV
|
void updateVisibility(); // Update gridstate from current FOV
|
||||||
|
@ -138,7 +112,7 @@ namespace mcrfpydef {
|
||||||
" name (str): Element name"),
|
" name (str): Element name"),
|
||||||
.tp_methods = UIEntity_all_methods,
|
.tp_methods = UIEntity_all_methods,
|
||||||
.tp_getset = UIEntity::getsetters,
|
.tp_getset = UIEntity::getsetters,
|
||||||
.tp_base = NULL,
|
.tp_base = &mcrfpydef::PyDrawableType,
|
||||||
.tp_init = (initproc)UIEntity::init,
|
.tp_init = (initproc)UIEntity::init,
|
||||||
.tp_new = PyType_GenericNew,
|
.tp_new = PyType_GenericNew,
|
||||||
};
|
};
|
||||||
|
|
|
@ -6,7 +6,6 @@
|
||||||
#include "UISprite.h"
|
#include "UISprite.h"
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
// UIDrawable methods now in UIBase.h
|
// UIDrawable methods now in UIBase.h
|
||||||
|
|
||||||
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
UIDrawable* UIFrame::click_at(sf::Vector2f point)
|
||||||
|
@ -432,9 +431,6 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
|
||||||
// Initialize children first
|
// Initialize children first
|
||||||
self->data->children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
|
self->data->children = std::make_shared<std::vector<std::shared_ptr<UIDrawable>>>();
|
||||||
|
|
||||||
// Initialize weak reference list
|
|
||||||
self->weakreflist = NULL;
|
|
||||||
|
|
||||||
// Define all parameters with defaults
|
// Define all parameters with defaults
|
||||||
PyObject* pos_obj = nullptr;
|
PyObject* pos_obj = nullptr;
|
||||||
PyObject* size_obj = nullptr;
|
PyObject* size_obj = nullptr;
|
||||||
|
@ -628,16 +624,6 @@ int UIFrame::init(PyUIFrameObject* self, PyObject* args, PyObject* kwds)
|
||||||
self->data->click_register(click_handler);
|
self->data->click_register(click_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Register in Python object cache
|
|
||||||
if (self->data->serial_number == 0) {
|
|
||||||
self->data->serial_number = PythonObjectCache::getInstance().assignSerial();
|
|
||||||
PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL);
|
|
||||||
if (weakref) {
|
|
||||||
PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref);
|
|
||||||
Py_DECREF(weakref); // Cache owns the reference now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -78,10 +78,6 @@ namespace mcrfpydef {
|
||||||
.tp_dealloc = (destructor)[](PyObject* self)
|
.tp_dealloc = (destructor)[](PyObject* self)
|
||||||
{
|
{
|
||||||
PyUIFrameObject* obj = (PyUIFrameObject*)self;
|
PyUIFrameObject* obj = (PyUIFrameObject*)self;
|
||||||
// Clear weak references
|
|
||||||
if (obj->weakreflist != NULL) {
|
|
||||||
PyObject_ClearWeakRefs(self);
|
|
||||||
}
|
|
||||||
obj->data.reset();
|
obj->data.reset();
|
||||||
Py_TYPE(self)->tp_free(self);
|
Py_TYPE(self)->tp_free(self);
|
||||||
},
|
},
|
||||||
|
@ -89,7 +85,7 @@ namespace mcrfpydef {
|
||||||
//.tp_hash = NULL,
|
//.tp_hash = NULL,
|
||||||
//.tp_iter
|
//.tp_iter
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Frame(pos=None, size=None, **kwargs)\n\n"
|
.tp_doc = PyDoc_STR("Frame(pos=None, size=None, **kwargs)\n\n"
|
||||||
"A rectangular frame UI element that can contain other drawable elements.\n\n"
|
"A rectangular frame UI element that can contain other drawable elements.\n\n"
|
||||||
"Args:\n"
|
"Args:\n"
|
||||||
|
@ -131,10 +127,7 @@ namespace mcrfpydef {
|
||||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
||||||
{
|
{
|
||||||
PyUIFrameObject* self = (PyUIFrameObject*)type->tp_alloc(type, 0);
|
PyUIFrameObject* self = (PyUIFrameObject*)type->tp_alloc(type, 0);
|
||||||
if (self) {
|
if (self) self->data = std::make_shared<UIFrame>();
|
||||||
self->data = std::make_shared<UIFrame>();
|
|
||||||
self->weakreflist = nullptr;
|
|
||||||
}
|
|
||||||
return (PyObject*)self;
|
return (PyObject*)self;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "UIGrid.h"
|
#include "UIGrid.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "McRFPy_API.h"
|
#include "McRFPy_API.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
#include <algorithm>
|
#include <algorithm>
|
||||||
// UIDrawable methods now in UIBase.h
|
// UIDrawable methods now in UIBase.h
|
||||||
|
|
||||||
|
@ -681,19 +680,6 @@ int UIGrid::init(PyUIGridObject* self, PyObject* args, PyObject* kwds) {
|
||||||
self->data->click_register(click_handler);
|
self->data->click_register(click_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize weak reference list
|
|
||||||
self->weakreflist = NULL;
|
|
||||||
|
|
||||||
// Register in Python object cache
|
|
||||||
if (self->data->serial_number == 0) {
|
|
||||||
self->data->serial_number = PythonObjectCache::getInstance().assignSerial();
|
|
||||||
PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL);
|
|
||||||
if (weakref) {
|
|
||||||
PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref);
|
|
||||||
Py_DECREF(weakref); // Cache owns the reference now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0; // Success
|
return 0; // Success
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -1408,15 +1394,7 @@ PyObject* UIEntityCollection::getitem(PyUIEntityCollectionObject* self, Py_ssize
|
||||||
std::advance(l_begin, index);
|
std::advance(l_begin, index);
|
||||||
auto target = *l_begin; //auto target = (*vec)[index];
|
auto target = *l_begin; //auto target = (*vec)[index];
|
||||||
|
|
||||||
// Check cache first to preserve derived class
|
// If the entity has a stored Python object reference, return that to preserve derived class
|
||||||
if (target->serial_number != 0) {
|
|
||||||
PyObject* cached = PythonObjectCache::getInstance().lookup(target->serial_number);
|
|
||||||
if (cached) {
|
|
||||||
return cached; // Already INCREF'd by lookup
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Legacy: If the entity has a stored Python object reference, return that to preserve derived class
|
|
||||||
if (target->self != nullptr) {
|
if (target->self != nullptr) {
|
||||||
Py_INCREF(target->self);
|
Py_INCREF(target->self);
|
||||||
return target->self;
|
return target->self;
|
||||||
|
|
18
src/UIGrid.h
18
src/UIGrid.h
|
@ -172,22 +172,18 @@ namespace mcrfpydef {
|
||||||
.tp_name = "mcrfpy.Grid",
|
.tp_name = "mcrfpy.Grid",
|
||||||
.tp_basicsize = sizeof(PyUIGridObject),
|
.tp_basicsize = sizeof(PyUIGridObject),
|
||||||
.tp_itemsize = 0,
|
.tp_itemsize = 0,
|
||||||
.tp_dealloc = (destructor)[](PyObject* self)
|
//.tp_dealloc = (destructor)[](PyObject* self)
|
||||||
{
|
//{
|
||||||
PyUIGridObject* obj = (PyUIGridObject*)self;
|
// PyUIGridObject* obj = (PyUIGridObject*)self;
|
||||||
// Clear weak references
|
// obj->data.reset();
|
||||||
if (obj->weakreflist != NULL) {
|
// Py_TYPE(self)->tp_free(self);
|
||||||
PyObject_ClearWeakRefs(self);
|
//},
|
||||||
}
|
|
||||||
obj->data.reset();
|
|
||||||
Py_TYPE(self)->tp_free(self);
|
|
||||||
},
|
|
||||||
//TODO - PyUIGrid REPR def:
|
//TODO - PyUIGrid REPR def:
|
||||||
.tp_repr = (reprfunc)UIGrid::repr,
|
.tp_repr = (reprfunc)UIGrid::repr,
|
||||||
//.tp_hash = NULL,
|
//.tp_hash = NULL,
|
||||||
//.tp_iter
|
//.tp_iter
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Grid(pos=None, size=None, grid_size=None, texture=None, **kwargs)\n\n"
|
.tp_doc = PyDoc_STR("Grid(pos=None, size=None, grid_size=None, texture=None, **kwargs)\n\n"
|
||||||
"A grid-based UI element for tile-based rendering and entity management.\n\n"
|
"A grid-based UI element for tile-based rendering and entity management.\n\n"
|
||||||
"Args:\n"
|
"Args:\n"
|
||||||
|
|
|
@ -1,7 +1,6 @@
|
||||||
#include "UISprite.h"
|
#include "UISprite.h"
|
||||||
#include "GameEngine.h"
|
#include "GameEngine.h"
|
||||||
#include "PyVector.h"
|
#include "PyVector.h"
|
||||||
#include "PythonObjectCache.h"
|
|
||||||
// UIDrawable methods now in UIBase.h
|
// UIDrawable methods now in UIBase.h
|
||||||
|
|
||||||
UIDrawable* UISprite::click_at(sf::Vector2f point)
|
UIDrawable* UISprite::click_at(sf::Vector2f point)
|
||||||
|
@ -29,42 +28,6 @@ UISprite::UISprite(std::shared_ptr<PyTexture> _ptex, int _sprite_index, sf::Vect
|
||||||
sprite = ptex->sprite(sprite_index, position, sf::Vector2f(_scale, _scale));
|
sprite = ptex->sprite(sprite_index, position, sf::Vector2f(_scale, _scale));
|
||||||
}
|
}
|
||||||
|
|
||||||
UISprite::UISprite(const UISprite& other)
|
|
||||||
: UIDrawable(other),
|
|
||||||
sprite_index(other.sprite_index),
|
|
||||||
sprite(other.sprite),
|
|
||||||
ptex(other.ptex)
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UISprite& UISprite::operator=(const UISprite& other) {
|
|
||||||
if (this != &other) {
|
|
||||||
UIDrawable::operator=(other);
|
|
||||||
sprite_index = other.sprite_index;
|
|
||||||
sprite = other.sprite;
|
|
||||||
ptex = other.ptex;
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
UISprite::UISprite(UISprite&& other) noexcept
|
|
||||||
: UIDrawable(std::move(other)),
|
|
||||||
sprite_index(other.sprite_index),
|
|
||||||
sprite(std::move(other.sprite)),
|
|
||||||
ptex(std::move(other.ptex))
|
|
||||||
{
|
|
||||||
}
|
|
||||||
|
|
||||||
UISprite& UISprite::operator=(UISprite&& other) noexcept {
|
|
||||||
if (this != &other) {
|
|
||||||
UIDrawable::operator=(std::move(other));
|
|
||||||
sprite_index = other.sprite_index;
|
|
||||||
sprite = std::move(other.sprite);
|
|
||||||
ptex = std::move(other.ptex);
|
|
||||||
}
|
|
||||||
return *this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/*
|
/*
|
||||||
void UISprite::render(sf::Vector2f offset)
|
void UISprite::render(sf::Vector2f offset)
|
||||||
{
|
{
|
||||||
|
@ -469,19 +432,6 @@ int UISprite::init(PyUISpriteObject* self, PyObject* args, PyObject* kwds)
|
||||||
self->data->click_register(click_handler);
|
self->data->click_register(click_handler);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Initialize weak reference list
|
|
||||||
self->weakreflist = NULL;
|
|
||||||
|
|
||||||
// Register in Python object cache
|
|
||||||
if (self->data->serial_number == 0) {
|
|
||||||
self->data->serial_number = PythonObjectCache::getInstance().assignSerial();
|
|
||||||
PyObject* weakref = PyWeakref_NewRef((PyObject*)self, NULL);
|
|
||||||
if (weakref) {
|
|
||||||
PythonObjectCache::getInstance().registerObject(self->data->serial_number, weakref);
|
|
||||||
Py_DECREF(weakref); // Cache owns the reference now
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -25,14 +25,6 @@ protected:
|
||||||
public:
|
public:
|
||||||
UISprite();
|
UISprite();
|
||||||
UISprite(std::shared_ptr<PyTexture>, int, sf::Vector2f, float);
|
UISprite(std::shared_ptr<PyTexture>, int, sf::Vector2f, float);
|
||||||
|
|
||||||
// Copy constructor and assignment operator
|
|
||||||
UISprite(const UISprite& other);
|
|
||||||
UISprite& operator=(const UISprite& other);
|
|
||||||
|
|
||||||
// Move constructor and assignment operator
|
|
||||||
UISprite(UISprite&& other) noexcept;
|
|
||||||
UISprite& operator=(UISprite&& other) noexcept;
|
|
||||||
void update();
|
void update();
|
||||||
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
void render(sf::Vector2f, sf::RenderTarget&) override final;
|
||||||
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
virtual UIDrawable* click_at(sf::Vector2f point) override final;
|
||||||
|
@ -90,10 +82,6 @@ namespace mcrfpydef {
|
||||||
.tp_dealloc = (destructor)[](PyObject* self)
|
.tp_dealloc = (destructor)[](PyObject* self)
|
||||||
{
|
{
|
||||||
PyUISpriteObject* obj = (PyUISpriteObject*)self;
|
PyUISpriteObject* obj = (PyUISpriteObject*)self;
|
||||||
// Clear weak references
|
|
||||||
if (obj->weakreflist != NULL) {
|
|
||||||
PyObject_ClearWeakRefs(self);
|
|
||||||
}
|
|
||||||
// release reference to font object
|
// release reference to font object
|
||||||
//if (obj->texture) Py_DECREF(obj->texture);
|
//if (obj->texture) Py_DECREF(obj->texture);
|
||||||
obj->data.reset();
|
obj->data.reset();
|
||||||
|
@ -103,7 +91,7 @@ namespace mcrfpydef {
|
||||||
//.tp_hash = NULL,
|
//.tp_hash = NULL,
|
||||||
//.tp_iter
|
//.tp_iter
|
||||||
//.tp_iternext
|
//.tp_iternext
|
||||||
.tp_flags = Py_TPFLAGS_DEFAULT | Py_TPFLAGS_BASETYPE,
|
.tp_flags = Py_TPFLAGS_DEFAULT,
|
||||||
.tp_doc = PyDoc_STR("Sprite(pos=None, texture=None, sprite_index=0, **kwargs)\n\n"
|
.tp_doc = PyDoc_STR("Sprite(pos=None, texture=None, sprite_index=0, **kwargs)\n\n"
|
||||||
"A sprite UI element that displays a texture or portion of a texture atlas.\n\n"
|
"A sprite UI element that displays a texture or portion of a texture atlas.\n\n"
|
||||||
"Args:\n"
|
"Args:\n"
|
||||||
|
@ -142,10 +130,7 @@ namespace mcrfpydef {
|
||||||
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
.tp_new = [](PyTypeObject* type, PyObject* args, PyObject* kwds) -> PyObject*
|
||||||
{
|
{
|
||||||
PyUISpriteObject* self = (PyUISpriteObject*)type->tp_alloc(type, 0);
|
PyUISpriteObject* self = (PyUISpriteObject*)type->tp_alloc(type, 0);
|
||||||
if (self) {
|
//if (self) self->data = std::make_shared<UICaption>();
|
||||||
self->data = std::make_shared<UISprite>();
|
|
||||||
self->weakreflist = nullptr;
|
|
||||||
}
|
|
||||||
return (PyObject*)self;
|
return (PyObject*)self;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
|
@ -1,215 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
"""Audit current constructor argument handling for all UI classes"""
|
|
||||||
|
|
||||||
import mcrfpy
|
|
||||||
import sys
|
|
||||||
|
|
||||||
def audit_constructors():
|
|
||||||
"""Test current state of all UI constructors"""
|
|
||||||
|
|
||||||
print("=== CONSTRUCTOR AUDIT ===\n")
|
|
||||||
|
|
||||||
# Create test scene and texture
|
|
||||||
mcrfpy.createScene("audit")
|
|
||||||
texture = mcrfpy.Texture("assets/test_portraits.png", 32, 32)
|
|
||||||
|
|
||||||
# Test Frame
|
|
||||||
print("1. Frame Constructor Tests:")
|
|
||||||
print("-" * 40)
|
|
||||||
|
|
||||||
# No args
|
|
||||||
try:
|
|
||||||
f = mcrfpy.Frame()
|
|
||||||
print("✓ Frame() - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Frame() - {e}")
|
|
||||||
|
|
||||||
# Traditional 4 args (x, y, w, h)
|
|
||||||
try:
|
|
||||||
f = mcrfpy.Frame(10, 20, 100, 50)
|
|
||||||
print("✓ Frame(10, 20, 100, 50) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Frame(10, 20, 100, 50) - {e}")
|
|
||||||
|
|
||||||
# Tuple pos + size
|
|
||||||
try:
|
|
||||||
f = mcrfpy.Frame((10, 20), (100, 50))
|
|
||||||
print("✓ Frame((10, 20), (100, 50)) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Frame((10, 20), (100, 50)) - {e}")
|
|
||||||
|
|
||||||
# Keywords
|
|
||||||
try:
|
|
||||||
f = mcrfpy.Frame(pos=(10, 20), size=(100, 50))
|
|
||||||
print("✓ Frame(pos=(10, 20), size=(100, 50)) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Frame(pos=(10, 20), size=(100, 50)) - {e}")
|
|
||||||
|
|
||||||
# Test Grid
|
|
||||||
print("\n2. Grid Constructor Tests:")
|
|
||||||
print("-" * 40)
|
|
||||||
|
|
||||||
# No args
|
|
||||||
try:
|
|
||||||
g = mcrfpy.Grid()
|
|
||||||
print("✓ Grid() - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Grid() - {e}")
|
|
||||||
|
|
||||||
# Grid size only
|
|
||||||
try:
|
|
||||||
g = mcrfpy.Grid((10, 10))
|
|
||||||
print("✓ Grid((10, 10)) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Grid((10, 10)) - {e}")
|
|
||||||
|
|
||||||
# Grid size + texture
|
|
||||||
try:
|
|
||||||
g = mcrfpy.Grid((10, 10), texture)
|
|
||||||
print("✓ Grid((10, 10), texture) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Grid((10, 10), texture) - {e}")
|
|
||||||
|
|
||||||
# Full positional (expected: pos, size, grid_size, texture)
|
|
||||||
try:
|
|
||||||
g = mcrfpy.Grid((0, 0), (320, 320), (10, 10), texture)
|
|
||||||
print("✓ Grid((0, 0), (320, 320), (10, 10), texture) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Grid((0, 0), (320, 320), (10, 10), texture) - {e}")
|
|
||||||
|
|
||||||
# Keywords
|
|
||||||
try:
|
|
||||||
g = mcrfpy.Grid(pos=(0, 0), size=(320, 320), grid_size=(10, 10), texture=texture)
|
|
||||||
print("✓ Grid(pos=..., size=..., grid_size=..., texture=...) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Grid(pos=..., size=..., grid_size=..., texture=...) - {e}")
|
|
||||||
|
|
||||||
# Test Sprite
|
|
||||||
print("\n3. Sprite Constructor Tests:")
|
|
||||||
print("-" * 40)
|
|
||||||
|
|
||||||
# No args
|
|
||||||
try:
|
|
||||||
s = mcrfpy.Sprite()
|
|
||||||
print("✓ Sprite() - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Sprite() - {e}")
|
|
||||||
|
|
||||||
# Position only
|
|
||||||
try:
|
|
||||||
s = mcrfpy.Sprite((10, 20))
|
|
||||||
print("✓ Sprite((10, 20)) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Sprite((10, 20)) - {e}")
|
|
||||||
|
|
||||||
# Position + texture
|
|
||||||
try:
|
|
||||||
s = mcrfpy.Sprite((10, 20), texture)
|
|
||||||
print("✓ Sprite((10, 20), texture) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Sprite((10, 20), texture) - {e}")
|
|
||||||
|
|
||||||
# Position + texture + sprite_index
|
|
||||||
try:
|
|
||||||
s = mcrfpy.Sprite((10, 20), texture, 5)
|
|
||||||
print("✓ Sprite((10, 20), texture, 5) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Sprite((10, 20), texture, 5) - {e}")
|
|
||||||
|
|
||||||
# Keywords
|
|
||||||
try:
|
|
||||||
s = mcrfpy.Sprite(pos=(10, 20), texture=texture, sprite_index=5)
|
|
||||||
print("✓ Sprite(pos=..., texture=..., sprite_index=...) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Sprite(pos=..., texture=..., sprite_index=...) - {e}")
|
|
||||||
|
|
||||||
# Test Caption
|
|
||||||
print("\n4. Caption Constructor Tests:")
|
|
||||||
print("-" * 40)
|
|
||||||
|
|
||||||
# No args
|
|
||||||
try:
|
|
||||||
c = mcrfpy.Caption()
|
|
||||||
print("✓ Caption() - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Caption() - {e}")
|
|
||||||
|
|
||||||
# Text only
|
|
||||||
try:
|
|
||||||
c = mcrfpy.Caption("Hello")
|
|
||||||
print("✓ Caption('Hello') - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Caption('Hello') - {e}")
|
|
||||||
|
|
||||||
# Position + text (expected order: pos, font, text)
|
|
||||||
try:
|
|
||||||
c = mcrfpy.Caption((10, 20), "Hello")
|
|
||||||
print("✓ Caption((10, 20), 'Hello') - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Caption((10, 20), 'Hello') - {e}")
|
|
||||||
|
|
||||||
# Position + font + text
|
|
||||||
try:
|
|
||||||
c = mcrfpy.Caption((10, 20), 16, "Hello")
|
|
||||||
print("✓ Caption((10, 20), 16, 'Hello') - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Caption((10, 20), 16, 'Hello') - {e}")
|
|
||||||
|
|
||||||
# Keywords
|
|
||||||
try:
|
|
||||||
c = mcrfpy.Caption(pos=(10, 20), font=16, text="Hello")
|
|
||||||
print("✓ Caption(pos=..., font=..., text=...) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Caption(pos=..., font=..., text=...) - {e}")
|
|
||||||
|
|
||||||
# Test Entity
|
|
||||||
print("\n5. Entity Constructor Tests:")
|
|
||||||
print("-" * 40)
|
|
||||||
|
|
||||||
# No args
|
|
||||||
try:
|
|
||||||
e = mcrfpy.Entity()
|
|
||||||
print("✓ Entity() - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Entity() - {e}")
|
|
||||||
|
|
||||||
# Grid position only
|
|
||||||
try:
|
|
||||||
e = mcrfpy.Entity((5.0, 6.0))
|
|
||||||
print("✓ Entity((5.0, 6.0)) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Entity((5.0, 6.0)) - {e}")
|
|
||||||
|
|
||||||
# Grid position + texture
|
|
||||||
try:
|
|
||||||
e = mcrfpy.Entity((5.0, 6.0), texture)
|
|
||||||
print("✓ Entity((5.0, 6.0), texture) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Entity((5.0, 6.0), texture) - {e}")
|
|
||||||
|
|
||||||
# Grid position + texture + sprite_index
|
|
||||||
try:
|
|
||||||
e = mcrfpy.Entity((5.0, 6.0), texture, 3)
|
|
||||||
print("✓ Entity((5.0, 6.0), texture, 3) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Entity((5.0, 6.0), texture, 3) - {e}")
|
|
||||||
|
|
||||||
# Keywords
|
|
||||||
try:
|
|
||||||
e = mcrfpy.Entity(grid_pos=(5.0, 6.0), texture=texture, sprite_index=3)
|
|
||||||
print("✓ Entity(grid_pos=..., texture=..., sprite_index=...) - works")
|
|
||||||
except Exception as e:
|
|
||||||
print(f"✗ Entity(grid_pos=..., texture=..., sprite_index=...) - {e}")
|
|
||||||
|
|
||||||
print("\n=== AUDIT COMPLETE ===")
|
|
||||||
|
|
||||||
# Run audit
|
|
||||||
try:
|
|
||||||
audit_constructors()
|
|
||||||
print("\nPASS")
|
|
||||||
sys.exit(0)
|
|
||||||
except Exception as e:
|
|
||||||
print(f"\nFAIL: {e}")
|
|
||||||
import traceback
|
|
||||||
traceback.print_exc()
|
|
||||||
sys.exit(1)
|
|
|
@ -1,30 +0,0 @@
|
||||||
#!/usr/bin/env python3
|
|
||||||
# Count format string characters
|
|
||||||
|
|
||||||
fmt = "|OOOOfOOifizfffi"
|
|
||||||
print(f"Format string: {fmt}")
|
|
||||||
|
|
||||||
# Remove the | prefix
|
|
||||||
fmt_chars = fmt[1:]
|
|
||||||
print(f"Format chars after |: {fmt_chars}")
|
|
||||||
print(f"Length: {len(fmt_chars)}")
|
|
||||||
|
|
||||||
# Count each type
|
|
||||||
o_count = fmt_chars.count('O')
|
|
||||||
f_count = fmt_chars.count('f')
|
|
||||||
i_count = fmt_chars.count('i')
|
|
||||||
z_count = fmt_chars.count('z')
|
|
||||||
s_count = fmt_chars.count('s')
|
|
||||||
|
|
||||||
print(f"\nCounts:")
|
|
||||||
print(f"O (objects): {o_count}")
|
|
||||||
print(f"f (floats): {f_count}")
|
|
||||||
print(f"i (ints): {i_count}")
|
|
||||||
print(f"z (strings): {z_count}")
|
|
||||||
print(f"s (strings): {s_count}")
|
|
||||||
print(f"Total: {o_count + f_count + i_count + z_count + s_count}")
|
|
||||||
|
|
||||||
# List out each position
|
|
||||||
print("\nPosition by position:")
|
|
||||||
for i, c in enumerate(fmt_chars):
|
|
||||||
print(f"{i+1}: {c}")
|
|
|
@ -0,0 +1,42 @@
|
||||||
|
#!/bin/bash
|
||||||
|
# Run all tests and check for failures
|
||||||
|
|
||||||
|
TESTS=(
|
||||||
|
"test_click_init.py"
|
||||||
|
"test_drawable_base.py"
|
||||||
|
"test_frame_children.py"
|
||||||
|
"test_sprite_texture_swap.py"
|
||||||
|
"test_timer_object.py"
|
||||||
|
"test_timer_object_fixed.py"
|
||||||
|
)
|
||||||
|
|
||||||
|
echo "Running all tests..."
|
||||||
|
echo "===================="
|
||||||
|
|
||||||
|
failed=0
|
||||||
|
passed=0
|
||||||
|
|
||||||
|
for test in "${TESTS[@]}"; do
|
||||||
|
echo -n "Running $test... "
|
||||||
|
if timeout 5 ./mcrogueface --headless --exec ../tests/$test > /tmp/test_output.txt 2>&1; then
|
||||||
|
if grep -q "FAIL\|✗" /tmp/test_output.txt; then
|
||||||
|
echo "FAILED"
|
||||||
|
echo "Output:"
|
||||||
|
cat /tmp/test_output.txt | grep -E "✗|FAIL|Error|error" | head -10
|
||||||
|
((failed++))
|
||||||
|
else
|
||||||
|
echo "PASSED"
|
||||||
|
((passed++))
|
||||||
|
fi
|
||||||
|
else
|
||||||
|
echo "TIMEOUT/CRASH"
|
||||||
|
((failed++))
|
||||||
|
fi
|
||||||
|
done
|
||||||
|
|
||||||
|
echo "===================="
|
||||||
|
echo "Total: $((passed + failed)) tests"
|
||||||
|
echo "Passed: $passed"
|
||||||
|
echo "Failed: $failed"
|
||||||
|
|
||||||
|
exit $failed
|
|
@ -0,0 +1,102 @@
|
||||||
|
import json
|
||||||
|
from time import time
|
||||||
|
#with open("/home/john/issues.json", "r") as f:
|
||||||
|
# data = json.loads(f.read())
|
||||||
|
#with open("/home/john/issues2.json", "r") as f:
|
||||||
|
# data.extend(json.loads(f.read()))
|
||||||
|
|
||||||
|
print("Fetching issues...", end='')
|
||||||
|
start = time()
|
||||||
|
from gitea import Gitea, Repository, Issue
|
||||||
|
g = Gitea("https://gamedev.ffwf.net/gitea", token_text="3b450f66e21d62c22bb9fa1c8b975049a5d0c38d")
|
||||||
|
repo = Repository.request(g, "john", "McRogueFace")
|
||||||
|
issues = repo.get_issues()
|
||||||
|
dur = time() - start
|
||||||
|
print(f"({dur:.1f}s)")
|
||||||
|
print("Gitea Version: " + g.get_version())
|
||||||
|
print("API-Token belongs to user: " + g.get_user().username)
|
||||||
|
|
||||||
|
data = [
|
||||||
|
{
|
||||||
|
"labels": i.labels,
|
||||||
|
"body": i.body,
|
||||||
|
"number": i.number,
|
||||||
|
}
|
||||||
|
for i in issues
|
||||||
|
]
|
||||||
|
|
||||||
|
input()
|
||||||
|
|
||||||
|
def front_number(txt):
|
||||||
|
if not txt[0].isdigit(): return None
|
||||||
|
number = ""
|
||||||
|
for c in txt:
|
||||||
|
if not c.isdigit():
|
||||||
|
break
|
||||||
|
number += c
|
||||||
|
return int(number)
|
||||||
|
|
||||||
|
def split_any(txt, splitters):
|
||||||
|
tokens = []
|
||||||
|
txt = [txt]
|
||||||
|
for s in splitters:
|
||||||
|
for t in txt:
|
||||||
|
tokens.extend(t.split(s))
|
||||||
|
txt = tokens
|
||||||
|
tokens = []
|
||||||
|
return txt
|
||||||
|
|
||||||
|
def find_refs(txt):
|
||||||
|
tokens = [tok for tok in split_any(txt, ' ,;\t\r\n') if tok.startswith('#')]
|
||||||
|
return [front_number(tok[1:]) for tok in tokens]
|
||||||
|
|
||||||
|
from collections import defaultdict
|
||||||
|
issue_relations = defaultdict(list)
|
||||||
|
|
||||||
|
nodes = set()
|
||||||
|
|
||||||
|
for issue in data:
|
||||||
|
#refs = issue['body'].split('#')[1::2]
|
||||||
|
|
||||||
|
#refs = [front_number(r) for r in refs if front_number(r) is not None]
|
||||||
|
refs = find_refs(issue['body'])
|
||||||
|
print(issue['number'], ':', refs)
|
||||||
|
issue_relations[issue['number']].extend(refs)
|
||||||
|
nodes.add(issue['number'])
|
||||||
|
for r in refs:
|
||||||
|
nodes.add(r)
|
||||||
|
issue_relations[r].append(issue['number'])
|
||||||
|
|
||||||
|
|
||||||
|
# Find issue labels
|
||||||
|
issue_labels = {}
|
||||||
|
for d in data:
|
||||||
|
labels = [l['name'] for l in d['labels']]
|
||||||
|
#print(d['number'], labels)
|
||||||
|
issue_labels[d['number']] = labels
|
||||||
|
|
||||||
|
import networkx as nx
|
||||||
|
import matplotlib.pyplot as plt
|
||||||
|
|
||||||
|
relations = nx.Graph()
|
||||||
|
|
||||||
|
for k in issue_relations:
|
||||||
|
relations.add_node(k)
|
||||||
|
for r in issue_relations[k]:
|
||||||
|
relations.add_edge(k, r)
|
||||||
|
relations.add_edge(r, k)
|
||||||
|
|
||||||
|
#nx.draw_networkx(relations)
|
||||||
|
|
||||||
|
pos = nx.spring_layout(relations)
|
||||||
|
nx.draw_networkx_nodes(relations, pos,
|
||||||
|
nodelist = [n for n in issue_labels if 'Alpha Release Requirement' in issue_labels[n]],
|
||||||
|
node_color="tab:red")
|
||||||
|
nx.draw_networkx_nodes(relations, pos,
|
||||||
|
nodelist = [n for n in issue_labels if 'Alpha Release Requirement' not in issue_labels[n]],
|
||||||
|
node_color="tab:blue")
|
||||||
|
nx.draw_networkx_edges(relations, pos,
|
||||||
|
edgelist = relations.edges()
|
||||||
|
)
|
||||||
|
nx.draw_networkx_labels(relations, pos, {i: str(i) for i in relations.nodes()})
|
||||||
|
plt.show()
|
Loading…
Reference in New Issue