Refactor: Timer-basierte Ticks durch Event-Queue ersetzen (Crops, Seedlings, TileRecovery) #36
Reference in New Issue
Block a user
No description provided.
Delete Branch "%!s()"
Deleting a branch is permanent. Although the deleted branch may continue to exist for a short time before it actually gets removed, it CANNOT be undone in most cases. Continue?
Hintergrund & Problem
Aktuell werden in
StateManagerdrei Methoden aufgerufen, die jeden Frame alle vorhandenen Einträge iterieren, nur um deren Countdown-Timer umdeltazu reduzieren:Das bedeutet: selbst wenn gerade kein einziges Crop kurz davor ist zu wachsen, wird trotzdem jedes Frame
Object.values(this.state.world.crops)aufgerufen — ein neues Array wird alloziert, jedes Element wird angefasst, nichts passiert. Bei wenigen Crops ist das harmlos. Wenn aber viele Crops/Seedlings gleichzeitig wachsen (z.B. Förster pflanzt massenhaft Setzlinge), wächst der Aufwand linear mit der Anzahl der Einträge. Das ist unnötig.Der Kern des Problems: Wir pollen jeden Frame, obwohl wir im Voraus wissen, wann sich etwas ändern wird.
Das bessere Pattern: Absoluter Zeitstempel + sortierter Event-Queue
Anstatt einen Countdown rückwärts zu zählen (
stageTimerMs -= delta), speichern wir wann ein Eintrag als nächstes fällig ist (growsAt: number). StateManager hält einen akkumuliertengameTime-Wert. Jedes Frame wird nur gefragt: „Welche Einträge sind jetzt fällig?" — das sind nur die am Anfang der sortierten Queue.Visualisierung
Heute (Polling):
Neu (Event-Queue):
Bei 100 Crops: heute 100 Checks pro Frame. Neu: 0–1 Checks pro Frame (nur die fälligen).
Betroffene Stellen
1.
CropState(types.ts)2.
TreeSeedlingState(types.ts)3.
tileRecovery(GameState in types.ts / StateManager)4.
StateManagergameTime: number— akkumulierter Spielzeit-Wert, wird durch alle drei Tick-Methoden inkrementiertcropQueue: Array<{ id: string; growsAt: number }>seedlingQueue: Array<{ id: string; growsAt: number }>tileRecoveryQueue: Array<{ key: string; growsAt: number }>enqueueCrop(id)/enqueueSeedling(id)/enqueueTileRecovery(key)rebuildQueues()— wird beim Laden aufgerufentickCrops(gameTime)→ drain queue front, keinObject.values()mehrtickSeedlings(gameTime)→ gleichtickTileRecovery(gameTime)→ gleich5. Save-Kompatibilität
Ältere Saves haben
stageTimerMsstattgrowsAt. Beim Laden prüfen:6. Watering-Sonderfall (FarmingSystem)
Crops können bewässert werden (
watered: true), was die Wachstumsgeschwindigkeit verdoppelt. Heute:delta * (crop.watered ? 2 : 1). Neu: beim BewässerngrowsAtneu berechnen:Die Queue muss danach neu sortiert werden (oder das betroffene Element neu eingereiht).
Implementierungsplan
Schritt 1 —
gameTime-Akkumulator in StateManagerprivate gameTime = 0hinzufügenadvanceTime(delta: number)oder direkt in den bestehenden Tick-Methoden erhöhenSchritt 2 — Types anpassen
CropState:stageTimerMs→growsAtTreeSeedlingState:stageTimerMs→growsAtSchritt 3 — Queue-Infrastruktur in StateManager
rebuildQueues()— iteriert gespeicherten State, baut alle drei Queues auf (nur beim Laden/Start)insertSorted(queue, entry)— O(log n) Binary-Search-InsertSchritt 4 — Tick-Methoden umschreiben
Schritt 5 — Aufrufer anpassen
GameScene.update():stateManager.advanceTime(delta)aufrufen, dann Tick-Methoden ohnedeltaFarmingSystem: Watering-Logik auf neuegrowsAt-Berechnung umstellenSchritt 6 — Save/Load
StateManager.load(): nach dem LadenrebuildQueues()aufrufenstageTimerMs-FelderWas wir NICHT ändern
growsAtist ein einfachernumberwiestageTimerMsOffene Fragen für die Diskussion
gameTimeüber Saves hinweg akkumulieren (d.h. persistiert werden), oder bei jedem Spielstart bei 0 anfangen undgrowsAtrelativ zu „Ladezeit + Rest" initialisieren?tileRecoveryauch umgestellt werden, oder erstmal nur Crops & Seedlings (da die häufiger/zahlreicher sind)?