Commit Graph
14 Commits
Author SHA1 Message Date
popertots f9a74ed432 feat: migrate quilter rendering to Bevy TilemapChunk
Replaces the CPU pixel-baking quilter system (QuiltCache, TerrainSprite,
pixel_buffers) with Bevy's native TilemapChunk GPU-index-lookup API.

Architecture:
- TilemapChunk entities replace TerrainSprite entities per (chunk, z, layer)
- Tileset PNG stacked vertically as texture_2d_array (11 rows: 6 floor + 5 fixture)
- populate_chunk_tiles reads TileMap HashMap directly; no ECS FloorTile entities
- Chunk spawn deferred to apply_terrain_blobs (after TileMap data exists)
- Dirty key system triggers repopulate on occlusion/camera-z changes

Bug fixes:
- populate_chunk_tiles: add chunk_pos offset to world tile lookups (was always (0,0))
- spawn_tilemap_chunks: offset Transform by -TILE_SIZE/2 (tile-centre vs bottom-left)
- spawn_tilemap_chunks: AlphaMode2d::Blend (was Opaque, blocking DF fade)
- update_tilemap_chunk_visibility: actually mutate Visibility (was read-only)
- handle_tile_occlusion_updates: mark dirty keys inline (was double-reading events)
- TilemapChunkSpawner: deduplicate pending queue and guard stale registry

Performance:
- ~115K FloorTile ECS entities eliminated
- GPU memory: O(tile_data) instead of O(CHUNK_TILES² × z_levels × pixel_bytes)
- Z-scroll: only TilemapChunkTileData repopulates (no entity re-spawn)
- Benchmarks via TilemapBenchmark resource (F9 to report)
2026-03-20 14:02:04 +00:00
popertots d7cbbaa04b feat: implement chunk unloading infrastructure
Chunk unloading system with cleanly abstracted decision layer.

- ChunkMap: replace (bool, i32) timer tuple with () presence marker;
  remove FIFO cycle fields pending_unload/unload_cycle/unload_cursor/
  unload_countdown. loaded_chunks is now HashMap<IVec2, ()>.
- handle_chunk_events: simplify re-entrancy check to contains_key;
  remove parallel Mutex dedup machinery — load detection is now trivial.
- is_standable: single HashMap get; return false for unloaded chunks
  instead of falling back to is_standable_slow (pathfinding correctness).
- remove_chunk_data: bounds-iteration tile cleanup for chunk unload.
- ChunkOwner component + chunk_entity_index: static terrain entities
  (floor tiles, fixtures, trees) tagged at spawn and despawned by
  chunk. Mobile entities (dorfs, pigs, rabbits) not tracked — safe
  by design since they were never indexed.
- unload_chunk: canonical single-chunk unload function; cleans
  loaded_chunks, despawns entities, removes tilemap data, marks render
  chunk dirty via ChunkZKey::from_world, updates connectivity, fires
  rebake. Full docstring with step-by-step state map, working example
  for dynamic unload, and "what it does NOT handle" section.
- dynamic_chunk_unloading_system: no-op stub. Re-enable by writing
  a system that diffs wanted vs loaded chunks and calls unload_chunk.
  Wired out of FixedUpdate during foundation work.
- render chunk fix: ChunkZKey::from_world now pub(crate) so unload
  can derive the correct render chunk key (spans 4x4 world chunks)
  matching what build_quilted_terrain_sprites uses at spawn.
- FloorTilePrefab::spawn: pre-existing bug — return Entity not ().
2026-03-20 10:30:33 +00:00
popertots 9535d65bd7 feat: data-oriented chunks optimization with tile registry
- Phase 1: Bit-packed standability (ChunkData with bitsets)
- Phase 2: Reactive connectivity (dirty chunks)
- Phase 3: Async terrain baking (AsyncComputeTaskPool)
- Pathfinding weight system (rock=50 preferred, bedrock=150 avoided)
- Movement speed affected by tile weight
- External tiles.toml for hot-reloadable tile definitions
- TileRegistry singleton for async-safe tile lookups
- Fixed world_to_chunk to use CHUNK_SIZE_TILE (128) not CHUNK_SIZE (8)
- Fixed infinite spawner with Local<bool> state guards
- Fixed spawn coordinate grid alignment

Note: Zigzag pathfinding bug introduced - needs investigation
2026-03-19 19:15:36 +00:00
popertots 50788af3c7 feat(optimization): implement data-oriented chunk architecture
Phase 1: Bit-packed standability
- Add ChunkData struct with 4 bitsets per chunk (stand_in/on for floor/fixture)
- Replace 4 HashMap lookups per standability check with O(1) bit operations
- Memory: ~2KB bitsets per chunk vs ~50KB HashMap overhead

Phase 2: Reactive connectivity
- Add dirty_chunks HashSet to ChunkMap for incremental updates
- update_chunk_connectivity now O(d) where d = dirty chunks
- Early exit when no changes, preventing O(N) full rebuilds

Phase 3: Async terrain baking
- Move terrain generation to AsyncComputeTaskPool
- spawn_terrain_tasks: non-blocking task spawn (~34µs)
- apply_terrain_blobs: batched entity spawn on main thread
- Eliminates main-thread stutters during world generation
2026-03-19 17:01:51 +00:00
popertots a5154f73dd docs(pathfinding): add documentation and remove unused async infrastructure
- Add comprehensive module documentation explaining tier architecture
- Add TileMap documentation explaining design choices (FxHashMap, packed data)
- Remove unused async pathfinding code (StandableTileSnapshot, spawn_async_path_task)
- Remove unused imports (check_ready, rayon, StdHashMap)
- Fix unused variable warnings with underscore prefixes
- Add #[allow(dead_code)] for intentionally unused fields
2026-03-18 22:08:27 +00:00
popertots 00cbd92491 refactor: simplify to time-sliced synchronous path queue
The async pathfinding with StandableBitGrid caused massive lag due to synchronous
bounding box calculation in the main thread (millions of hashmap lookups for long paths).
Additionally, the splicing logic had a catastrophic bug where it applied a single
finished path to ALL entities unconditionally.

Solution:
- Revert to thread-local synchronous A*
- Implement PathRequestQueue to process max 8 paths per frame
- Keep provisional paths for immediate movement
- Since entity walks provisional path, full path calculates from current position,
  eliminating the need for complex splicing logic.
2026-03-18 17:23:48 +00:00
popertots c902cff908 feat: async pathfinding with provisional paths and bit-grid snapshots
- Add StandableBitGrid: O(1) bit-packed snapshot (~6KB per 50k tiles vs HashMap overhead)
- Implement two-tier pathfinding: sync for short paths (<64 tiles), provisional+async for long paths
- calculate_provisional_path: capped A* returning path to best heuristic node
- calculate_async_path: A* using bit-grid (Send+Sync, no thread_local)
- prepare_paths system: dispatches provisional paths immediately, spawns async for full paths
- poll_async_paths + splice_completed_async_paths: seamless path transition when async completes
- Entities start walking immediately on provisional path while full path computes in background

Architecture:
  FixedUpdate: prepare_paths → update_wandering_targets → movement
  PostUpdate: poll_async_paths → splice_completed_async_paths

Priority: DF-like pathing (immediate movement) > performance > memory
2026-03-18 16:35:39 +00:00
popertots 367ab26d5e Fix performance regressions: remove TIER0, consolidate scratchpads, remove Arc trap
Key fixes based on benchmark analysis:
1. Remove TIER0 Vec-based pathfinding - O(N) linear scan was slower than FxHashMap for typical path lengths
2. Consolidate scratchpads into single AStarScratchpad struct - eliminates nested RefCell borrow overhead
3. Remove Arc wrapper from TileMap - eliminated Copy-on-Write trap causing 63ms stutters
4. Replace AHashMap with FxHashMap - FxHash is faster for small integer keys like IVec3
5. Simplify tier logic - single pathfinding function with scratchpad reuse

Benchmark analysis showed:
- Original: 1.95 µs/node, P99 1.1ms, Max 5ms
- TIER0/TIER1 regression: 2.89 µs/node (+48%), P99 5ms (+348%)
- Root causes: Vec linear scan in TIER0, nested RefCell borrows, Arc::make_mut CoW

This should restore and improve performance by using simple FxHashMap scratchpad for all paths.
2026-03-18 15:30:48 +00:00
popertots 466a20700a Wrap TileMap HashMaps in Arc for async pathfinding access
- Add Arc<AHashMap> wrapper around floor_tiles, fixture_tiles, item_tiles
- Copy-on-write semantics: Arc::make_mut clones only if other Arcs exist
- Add insert_floor, insert_fixture, insert_item, remove_item methods
- Add get_floor_mut, get_fixture_mut for visibility updates
- Update all mutation sites to use new TileMap methods
- Enables cheap Arc::clone for async pathfinding workers
- Single-threaded pathfinding: no clone, direct access
- Multi-threaded pathfinding: clone Arc, read without locks
2026-03-18 14:56:57 +00:00
popertots 8478b385bc Optimize TileMap: replace HashMap with AHashMap, pack tile data
- Replace std HashMap (SipHash) with ahash::AHashMap for fast non-crypto hashing
- Pack tile data from tuples to structs: FloorTileData (~35 bytes) and FixtureTileData (~18 bytes)
- FloorTileData: pack 3 bools into single flags byte, use u8 for id/weight
- FixtureTileData: pack 2 bools into single flags byte
- Update all accessors: is_standable_tile, visibility, terrain generation, forestry
- Preparation for async pathfinding (Arc wrapping to come in follow-up)

Memory reduction: ~54% for floor tiles (76→35 bytes), ~62% for fixtures (48→18 bytes)
Hash performance: AHashMap uses fxhash, faster than SipHash for game data
2026-03-18 14:52:46 +00:00
popertots 886d162963 dropped item rotation 2026-02-15 18:56:38 +00:00
popertots 975ac20876 minor 2025-09-14 19:04:34 +01:00
popertots 4c4c985be3 item flashing 2025-09-14 18:48:50 +01:00
popertots ee7ac39ad5 refactor file locations for readability 2025-09-11 18:23:53 +01:00